Streams model repeated async events such as user input, sockets, database watches and Flutter's StreamBuilder.
A `Stream<T>` delivers a sequence of asynchronous events — data, errors, and a final done. Where a Future gives one value, a stream gives many.
Consuming:
await for (final event in stream) { ... }inside anasyncfunction, withtry/catchfor errors. The loop ends when the stream closes.stream.listen(onData, onError: ..., onDone: ...)returns aStreamSubscriptionyou canpause,resumeand mustcancel()when done (for example in a Flutterdispose).
Streams have iterable-like transformers: map, where, take, skip, distinct, asyncMap, expand, plus first, toList() and fold that return futures.
Producing:
- An **
async*generator** function withyield(andyield*to forward another stream). It's lazy: the body starts when someone listens and pauses when the listener pauses. - A `StreamController` when events come from elsewhere:
controller.add(x),addError,close(), and exposecontroller.stream.
Single-subscription streams (the default, e.g. file reads) allow exactly one listener and buffer events until it subscribes. Broadcast streams (StreamController.broadcast(), asBroadcastStream()) allow many listeners but drop events no one is listening to. Listening twice to a single-subscription stream throws a StateError.
For advanced operators (debounce, combineLatest), the rxdart and stream_transform packages are common.
Example
import 'dart:async';
Stream<int> countdown(int from) async* {
for (var i = from; i > 0; i--) {
await Future.delayed(const Duration(milliseconds: 10));
yield i;
}
}
Future<void> main() async {
await for (final n in countdown(3)) {
print(n); // 3 2 1
}
final evens = countdown(6).where((n) => n.isEven).map((n) => n * 10);
print(await evens.toList()); // [60, 40, 20]
final clicks = StreamController<String>.broadcast();
final sub = clicks.stream.listen((c) => print('A got $c'));
clicks.stream.where((c) => c == 'ok').listen((_) => print('B saw ok'));
clicks..add('ok')..add('cancel');
await Future<void>.delayed(Duration.zero); // let events deliver
await sub.cancel();
await clicks.close();
// A got ok / B saw ok / A got cancel
}Remember
- Consume with
await fororlisten, and cancel subscriptions - Produce with
async*/yieldor a StreamController - Single-subscription = one listener; broadcast = many, no buffering
Watch out for
- Never cancelling a
listensubscription, leaking memory and work after a widget is gone.