StreamController: The Faucet for Your Data Stream
A StreamController is the faucet handle for your data stream. Use it to create a custom stream from any data source and push events to it. The main footgun is that a default controller only allows one listener; use `StreamController.broadcast` for multiple.
WHY IT EXISTS Dart's Stream is a powerful abstraction for asynchronous data, but it's a read-only interface. You can listen to a stream, but you can't push new data into it directly. StreamController solves this by providing the "write" side, allowing you to create and manage the flow of events for a stream you control.
THE MENTAL MODEL Think of a StreamController as the faucet and valve system for a water pipe (the Stream). You, the programmer, use the controller to turn the water on (add), signal a problem in the line (addError), or shut it off for good (close). Listeners simply attach to the pipe's output to receive whatever comes through. The controller is the "write" interface; the stream is the "read" interface.
HOW IT WORKS You instantiate a StreamController. This object gives you two key properties: stream and sink. The stream property is the Stream object that others will listen to. The sink (or the controller itself, which implements StreamSink) is how you add events. You call controller.add(data) to send data, controller.addError(error) to send an error, and controller.close() to signal that no more events will be sent. The controller also provides callbacks like onListen, onPause, and onCancel to react to how listeners are interacting with your stream.
WHEN TO USE IT Use a StreamController whenever you need to create a Stream from a source that isn't already a stream. This is common for wrapping external events, like user button clicks in a UI, data from a websocket, or responses from a native platform channel. It's the bridge between event-based, non-stream code and the reactive world of Dart streams.
WHEN NOT TO USE IT Avoid StreamController if a more direct stream creation method exists. For example, to create a stream from a Future, use Stream.fromFuture(). For periodic events, use Stream.periodic(). The biggest footgun is using the wrong constructor: the default StreamController() creates a single-subscription stream. If more than one listener tries to subscribe, it will throw an error. If you need multiple listeners, you must use the StreamController.broadcast() factory constructor.
ONE CANONICAL EXAMPLE To create a controller, add data, and close it: final controller = StreamController<int>(); controller.stream.listen((data) => print('Event: $data'), onError: (err) => print(err)); controller.add(1); controller.add(999); controller.addError(Exception('Issue 101')); controller.close(); This setup creates a controller, listens to its stream, pushes two data events and one error event, and then closes it.
Read the original → api.flutter.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.