Skip to main content

I/O Library Now Uses Streams

We have done a full re-design of the dart:io API (file, network, directories, etc) to use the classes and concepts in the core library and in dart:async. This means that there is almost no callback registration in dart:io any more. All async operations now use streams and futures.

(These changes are available in bleeding_edge as of today, and should show up in a weekly build soon.)




We think this is a great step forward in aligning all of the “dart:” API's and for you to have fewer concepts to learn when using Dart for different tasks.

However this is a breaking change which requires rewriting code using dart:io. The good news is that most of these changes should be mechanical. The following sections describe these changes in more details and give some examples on how to migrate code to the new API. For more information take a look on the API documentation for dart:io.

Streams in dart:io
The classes InputStream and OutputStream are gone and have been replaced with classes implementing IOSink and Stream<List<int>>.

When reading from a Stream<List<int>> just use the listen method which have arguments matching the callbacks previously used. This shows how to migrate code using an InputStream to the streams-based API:

dart:io v1 code
InputStream stream = ...
stream.onData = () {
 var data = request.inputStream.read();
 /* Process data. */
};
stream.onClosed = () {
 /* All data received. */
};
stream.onError = (e) {
 /* Error on input. */
};

dart:io v2 code
Stream<List<int>> stream = ...
stream.listen(
 (data) { /* Process data. */ },
 onDone: () { /* All data received. */ },
 onError: (e) { /* Error on input. */ });

As the InputStream class is now gone so is the StringInputStream for turning a stream of bytes into strings and/or lines. Two new stream transformers StringDecoder and LineTransformer have been added to address this. The StringDecoder transforms a stream of List<int> to a stream of String and the LineTransformer transforms a stream of String to a new stream of String where each string is a line.

dart:io v1 code
InputStream stream = ...
StringInputStream stringStream = new StringInputStream(stream);
stringStream.onLine = () {
 String line = stringStream.readLine();
 /* Do something with line. */
};
stringStream.onClosed = () {
 /* No more lines */
};
stream.onError = (e) {
 /* Error on input. */
};

dart:io v2 code
Stream<<int>> stream = ...
stream
 .transform(new StringDecoder())
 .transform(new LineTransformer())
 .listen((String line) { /* Do something with line. */ },
         onDone: () { /* No more lines */ },
         onError: (e) { /* Error on input. */ });

The IOSink replaces OutputStream and this shows how to migrate code using OutputStream to use an IOSink:

dart:io v1 code
OutputStream stream = …
stream.write([72, 101, 108, 108, 111]);  // Hello
stream.writeString(", world!");
stream.close();

dart:io v2 code
IOSink sink = …
sink.add([72, 101, 108, 108, 111]);  // Hello
sink.addString(", world!");
sink.close();

The IOSink also allows you to pipe directly from a stream.

HTTP
The main changes to the HTTP server and client part of dart:io are the following:

* A new HttpServer listening for requests is created using the static method bind.
* An HttpServer is a stream of HttpRequests.
* The defaultRequestHandler setter and addRequestHandler method on HttpServer are gone.
* The HttpRequest and HttpClientResponse objects implement Stream<List<int>>.
* The HttpClientRequest and HttpResponse objects implement IOSink.

To create a new listening HTTP server use the static method bind which returns a future.

dart:io v1 code
HttpServer server = new HttpServer();
server.defaultRequestHandler = …
server.addRequestHandler(…);
server.listen(“127.0.0.1”, 8080);
// Now the HTTP server is bound and listening for requests.

dart:io v2 code
HttpServer.bind(“127.0.0.1”, 8080)
   .then((HttpServer server) {
     server.listen(
         (HttpRequest request) {
           // Handle the request.
         });
}

The request routing through defaultRequestHandler and addRequestHandler is gone and any routing to specific request handling methods should be added to the listen handling. The HttpResponse is available as the response property on the HttpRequest object.

For client side HTTP the HttpClient class still exists. The HttpClientConnection class is gone, and instead the request initiation methods get, post, open, etc. now returns a future for the HttpClientRequest object. The HttpClientRequest object has a response property which is a future for the HttpClientResponse. As a convenience the HttpClientRequest.close method also returns the future for the response. This shows how to migrate HTTP client code:

dart:io v1 code
HttpClient client = new HttpClient();
HttpClientConnection connection = client.get(...);
connection.onRequest = (HttpClientRequest request) {
 // Prepare the request.
 request.outputStream.close();
}
connection.onResponse = (HttpClientResponse response) {
 // Process the response.
}

dart:io v2 code
HttpClient client = new HttpClient();
client.get(...)
   .then((HttpClientRequest request) {
     // Prepare the request.
     return request.close();
   })
   .then((HttpClientResponse response) {
     // Process the response.
   });

Web Sockets
The web socket interface has been simplified and now uses the same class for web socket connections on both the client and the server. The WebSocket class is a stream of events.

On the server the web socket handling is implemented as a stream transformer. This transformer can transform a stream of HttpRequests into a stream of WebSockets.

dart:io v1 code
HttpServer server = new HttpServer();
server.listen(...);
WebSocketHandler handler = new WebSocketHandler();
handler.onOpen = (WebSocketConnection connection) {
 connection.onMessage = (Object message) {
   /* Handle message. */
 };
 connection.onClosed = (status, reason) {
   /* Handle closed. */
 };
};
server.defaultRequestHandler = handler.onRequest;

dart:io v2 code
HttpServer.bind(...).then((server) {
 server.transform(new WebSocketTransformer()).listen((WebSocket webSocket) {
   webSocket.listen((event) {
     if (event is MessageEvent) {
       /* Handle message. */
     } else if (event is CloseEvent) {
       /* Handle closed. */
     }
   });
 });

On the client connecting a web socket has become much simpler. Just use the WebSocket static method connect which returns a future for the web socket connection as shown here:

dart:io v1 code
HttpClient client = new HttpClient();
HttpClientConnection conn = client.openUrl(“https://127.0.0.1:8080”);
WebSocketClientConnection wsconn = new WebSocketClientConnection(conn);
wsconn.onMessage = (message) {
 /* Handle message. */
}
wsconn.onClosed = (status, reason) {
 /* Handle closed. */
};

dart:io v2 code

WebSocket.connect("ws://echo.websocket.org")
   .then((WebSocket webSocket) {
     webSocket.listen((message) {
         /* Handle message. */
       },
onDone: () {
         /* Handle closed. */
       });
   });

Process
The Process class uses Stream<List<int>> for stdout and stderr and IOSink for stdin. The exit code for the process is now available through the exitCode future.

dart:io v1 code
Process process = ...
process.stdout.onData = ...
process.stdout.onDone = ...
process.onExit = (exitCode) { /* do something with exitCode. */ }

dart:io v2 code
Process process = ...
process.stdout.listen(...);
p.exitCode.then((exitCode) { /* do something with exitCode. */ });

Likewise the types of the top level properties stdin, stdout and stderr have been changed. stdio is a Stream<List<int>> and stdout and stderr are IOSinks.

File and directory
Reading and writing a file also uses Stream<List<int>> and IOSink. To read a file change the use of openInputStream to openRead which returns a stream of type Stream<List<int>>. Likewise change the use of openOutputStream to openWrite which returns an IOSink.

The Directory.list function now returns a stream of FileSystemEntity objects. The FileSystemEntity is a superclass of both File and Directory. The previous DirectoryLister where callbacks were set is now gone.

dart:io v1 code
Directory dir = ...
DirectoryLister lister = dir.list();
lister.onDir = (Directory directory) { /* Do something with directory. */ };
lister.onFile = (File file) { /* Do something with file. */ };
lister.onDone = (bool complete) { /* Listing ended.*/ };
lister.onError = (error) { /* Listing error.*/ };


dart:io v2 code
Directory dir = ...
dir.list().listen(
   (FileSystemEntity fse) {
     if (fse is Directory) {
       Directory directory = fse;
       /* Do something with directory. */
     } else if (fse is File) {
       File file = fse;
       /* Do something with file. */
     }
   },
   onDone: () { /* Listing ended.*/ },
   onError: (error) { /* Listing error.*/ });

Sockets
The classes Socket and SecureSocket both implement Stream<List<int>> and IOSink as they support bidirectional communication. This replaces all the reading and writing previously provided through a combination of callbacks, read and write methods, and InputStream and OutputStream objects backed by sockets.

Connecting a socket now uses a static method returning a future instead of a callback.

dart:io v1 code
Socket socket = new Socket(host, port);
socket.onConnect = () { /* Socket connected. */ }

dart:io v2 code
Socket.connect(host, port).then((Socket socket) {
 /* Socket connected. */
};

The classes ServerSocket and SecureServerSocket now uses a static method returning a future when binding to an interface. A listening server socket delivers the socket connections as a stream instead of through the onConnection callback.

dart:io v1 code
ServerSocket socket = new ServerSocket(host, port);
socket.onConnection = (Socket clientSocket) {
 /* Do something with the clientSocket. */
}

dart:io v2 code
ServerSocket.bind(host, port).then((ServerSocket socket) {
 socket.listen((Socket clientSocket) {
   /* Do something with the clientSocket. */
 })
});

Raw sockets
In order to provide a low level socket API we introduced raw sockets. Raw sockets give you access to low-level socket events without giving you data in your hand (think of this as the events you get from epoll on a socket file descriptor on Linux systems). Based on the low-level events you can read out data from the socket and decide when to write more data to the socket. The high-level Socket and ServerSocket classes are built on top of the RawSocket and RawServerSocket classes. Check out the API documentation on the Raw* classes.



(Photo Credit: Marooned cc)

Popular posts from this blog

A stronger Dart for everyone

We are constantly asking ourselves:
How do we make developers even more productive when writing Dart apps? We believe that a critical part of the answer to this question is to make strongmode – a sound static type system for Dart – the standard for all Dart developers.

Teams that use Dart to build apps like Soundtrap, AdWords, AdSense, and Greentea say they really enjoy using strong mode features, such as early error detection. In fact, teams that have switched completely to strong mode cite not only early error detection but also better code readability and maintainability as major benefits. We hear this both from small teams and Рeven more so Рfrom large teams with hundreds of developers writing and maintaining millions of lines of Dart code. As Björn Sperber from Soundtrap says,
Strong mode and the smooth integration with IntelliJ is a joy to use and a huge improvement. If you’ve tried out Flutter, you’ve already used strong mode checks from the Dart analyzer.

Given the benefits …

AngularDart 4

AngularDart v4 is now available. We've been busy since the release angular2 v3.1.0 in May. Not only did we "drop the 2", but we also improved the compiler and tightened up the framework to give you smaller code, we updated the package structure to improve usability, and we added several new features. Check out the updated documentation to get started.
Just angular Upgrading to v4 will require more than updating your version constraint. The package has changed names (back) to angular – dropping the 2. You'll need to update your pubspec.yaml and the corresponding imports in your code. In most instances, find-and-replace should do the trick. Going forward, the package will be called package:angular. We'll just update the version number.
Smaller code The updated compiler in 4.0 allows type-based optimizations that not only improve runtime performance but generate better code because we are able to strongly type templates. A big result of the update is that many ap…

Dart 1.24: Faster edit-refresh cycle on the web & new function type syntax

Dart 1.24 is now available. It includes the Dart Development Compiler and supports a new generic function type syntax. Get it now!


Figure 1: DDC debugging in Chrome.

Some notable changes in this release:
pub serve now has support for the Dart Development Compiler. Unlike dart2js, this new compiler is modular, which allows pub to do incremental re-builds for pub serve.In practice what that means is you can edit your Dart files, refresh in Chrome (or other supported browsers), and see your edits almost immediately. This is because pub is only recompiling your package, not all packages that you depend on.There is one caveat with the new compiler, which is that your package and your dependencies must all be strong mode clean.You can also use the new compiler to run your tests in Chrome much more quickly than you can with dart2js.Read more in the changelog.You can now publish packages that depend on the Flutter SDK to pub. Moreover, pub.dartlang.org has started tagging Flutter plugins with …