Skip to content
AgileLogics
7 min read

Streams

Prerequisites: The event loop

A Future delivers one value later. A Stream delivers many values over time — user input events, messages from a server, lines from a file, location updates.

Creating a stream with async*

An async* function returns a stream and produces values with yield:

Stream<int> countdown(int from) async* {
  for (var i = from; i >= 1; i--) {
    await Future.delayed(const Duration(milliseconds: 100));
    yield i;
  }
}

Future<void> main() async {
  await for (final n in countdown(3)) {
    print(n);
  }

  final doubled = await countdown(4).map((n) => n * 2).where((n) => n > 2).toList();
  print(doubled);
}
3
2
1
[8, 6, 4]
  • await for processes each value as it arrives, finishing when the stream ends.
  • Streams have the familiar map, where, take, skip, and more. toList() waits for the stream to finish and collects everything.

Listening

listen subscribes with callbacks instead of a loop, and returns a subscription you can cancel:

Stream<int> ticks() async* {
  var i = 0;
  while (true) {
    await Future.delayed(const Duration(milliseconds: 100));
    yield i++;
  }
}

Future<void> main() async {
  final subscription = ticks().listen(
    (tick) => print('tick $tick'),
    onError: (Object e) => print('error: $e'),
    onDone: () => print('done'),
  );

  await Future.delayed(const Duration(milliseconds: 350));
  await subscription.cancel();
  print('cancelled');
}
tick 0
tick 1
tick 2
cancelled

Always cancel subscriptions you no longer need — for example, in a Flutter widget's dispose method. A forgotten subscription keeps running and holding memory.

Errors in streams

Stream<int> readings() async* {
  yield 21;
  yield 22;
  throw StateError('sensor disconnected');
}

Future<void> main() async {
  try {
    await for (final value in readings()) {
      print('reading: $value');
    }
  } on StateError catch (e) {
    print('stopped: ${e.message}');
  }
}
reading: 21
reading: 22
stopped: sensor disconnected

Single-subscription vs broadcast

Most streams allow only one listener. A broadcast stream allows many — the next lesson shows how to create both.

Common mistakes

  • Listening to a single-subscription stream twice: Bad state: Stream has already been listened to.
  • Forgetting to cancel subscriptions in long-lived apps.