Skip to content
DeveloperMemos

A Quick Tutorial on Dart Streams

Dart, Streams, Asynchronous Programming3 min read

Dart is a versatile programming language that offers powerful features for asynchronous programming. One such feature is Dart Streams, which provide a way to handle sequences of asynchronous events in an elegant and efficient manner. In this tutorial, we will dive into the world of Dart Streams and understand how they can simplify your asynchronous code. So let's get started!

What are Streams?

Streams in Dart represent sequences of asynchronous events. They allow you to process data as it becomes available over time, rather than waiting for all the data to be ready upfront. Streams are widely used for handling various types of asynchronous operations, such as network requests, file I/O, user input, and more.

A stream can emit multiple values over time, and you can listen to these values using stream listeners. The listeners receive events whenever new data is available or when the stream is done emitting events. This makes streams a powerful tool for handling asynchronous operations and reacting to real-time data.

Creating a Stream

To create a stream in Dart, you can use the Stream class provided by the dart:async library. Here's an example of creating a simple stream that emits a sequence of integers:

1import 'dart:async';
2
3void main() {
4 final stream = Stream<int>.fromIterable([1, 2, 3, 4, 5]);
5
6 stream.listen((int value) {
7 print(value);
8 });
9}

In this example, we create a stream using the Stream.fromIterable constructor, which takes an iterable as input. The listen method is then called on the stream to start listening for events. Whenever a new value is emitted by the stream, the provided callback function will be invoked.

Transforming Stream Data

Dart Streams provide several built-in methods that allow you to transform and manipulate the data emitted by a stream. These methods can be chained together to build complex data processing pipelines. Let's take a look at a few commonly used methods:

map: Applies a transformation to each event in the stream

The map method allows you to apply a transformation function to each event emitted by the stream. It returns a new stream that emits the transformed events. Here's an example that doubles the values of a stream of integers:

1import 'dart:async';
2
3void main() {
4 final stream = Stream<int>.fromIterable([1, 2, 3, 4, 5]);
5
6 stream.map((int value) => value * 2).listen((int doubledValue) {
7 print(doubledValue);
8 });
9}

In this example, the map method is used to transform each value emitted by the stream by multiplying it by 2. The resulting stream emits the doubled values, which are then printed.

where: Filters events based on a predicate

The where method allows you to filter events based on a given condition or predicate function. It returns a new stream that only emits the events satisfying the condition. Here's an example that filters out even numbers from a stream of integers:

1import 'dart:async';
2
3void main() {
4 final stream = Stream<int>.fromIterable([1, 2, 3, 4, 5]);
5
6 stream.where((int value) => value.isOdd).listen((int oddValue) {
7 print(oddValue);
8 });
9}

In this example, the where method filters out even numbers by checking if each value is odd. Only the odd values are emitted by the resulting stream.

asyncMap: Applies an asynchronous transformation to each event

The asyncMap method allows you to apply an asynchronous transformation function to each event in the stream. It returns a new stream that emits the transformed events when the transformations complete. Here's an example that performs an async HTTP request for each URL emitted by a stream:

1import 'dart:async';
2import 'package:http/http.dart' as http;
3
4void main() {
5 final stream = Stream<String>.fromIterable([
6 'https://example.com',
7 'https://google.com',
8 'https://openai.com'
9 ]);
10
11 stream.asyncMap((String url) => http.get(Uri.parse(url))).listen((response) {
12 print(response.body);
13 });
14}

In this example, the asyncMap method is used to perform an HTTP GET request for each URLemitted by the stream. The http.get function returns a Future representing the asynchronous operation, and the resulting stream emits the response when each request completes.

Handling Stream Errors

When working with streams, it's important to handle errors that may occur during asynchronous operations. Dart Streams provide the onError method, which allows you to specify an error handler for the stream. Here's an example:

1import 'dart:async';
2
3void main() {
4 final stream = Stream<int>.fromIterable([1, 2, 3]);
5
6 stream.map((int value) => value * 2).listen(
7 (int doubledValue) {
8 print(doubledValue);
9 },
10 onError: (error) {
11 print('Error occurred: $error');
12 }
13 );
14}

In this example, the onError callback is invoked if any error occurs during the stream processing. You can use this callback to handle and react to errors in a graceful manner.

Closing a Stream

Streams in Dart need to be closed once you're done using them to free up resources and prevent memory leaks. The StreamSubscription returned by the listen method provides a cancel method that you can call to close the stream. Alternatively, you can use the await for loop to automatically close the stream when all events have been processed. Here's an example:

1import 'dart:async';
2
3void main() async {
4 final stream = Stream<int>.fromIterable([1, 2, 3, 4, 5]);
5
6 final subscription = stream.listen((int value) {
7 print(value);
8 });
9
10 // Do some processing...
11
12 await subscription.cancel();
13}

In this example, the stream is closed by calling the cancel method on the subscription object. Alternatively, you can use await for loop syntax like this:

1import 'dart:async';
2
3void main() async {
4 final stream = Stream<int>.fromIterable([1, 2, 3, 4, 5]);
5
6 await for (int value in stream) {
7 print(value);
8 }
9}

With await for, the stream is automatically closed when all events have been processed.


Dart Streams provide a powerful tool for handling asynchronous events and data processing in an efficient and flexible manner. They allow you to work with sequences of events as they occur, enabling real-time data handling and reacting to changes in your applications. By understanding the basics of Dart Streams and utilizing their various methods, you can take full advantage of asynchronous programming in Dart.