What is a Stream in Flutter?
A Stream is a sequence of asynchronous events. It’s like a pipe where data flows over time – you can listen to the stream and react whenever a new event arrives. Streams are essential for handling real‑time updates, user input, network responses, and any situation where data arrives continuously or at unpredictable times. In Flutter, the StreamBuilder widget makes it easy to build UI that automatically updates when the stream emits new data.
Creating a Stream
There are several ways to create a stream:
StreamController: Manually add events (useful for custom event sources).
async*functions: Generate events usingyield(like a generator).
Stream.fromIterable: Create a stream from an existing collection.
Stream.periodic: Emit events at regular intervals.
Stream.fromFuture: Convert a singleFutureto a stream (only one event).
Using StreamController
The StreamController gives you a sink to add events and a stream to listen to. You can add data, errors, and close the stream when done.
Remember to dispose of the controller when you no longer need it to avoid memory leaks.
async* Generator Functions
An async* function automatically returns a Stream. Inside, you use yield to emit values. This is ideal for generating sequences on the fly.
Listening to a Stream with StreamBuilder
StreamBuilder is a Flutter widget that listens to a stream and rebuilds whenever new data arrives. It’s perfect for updating UI in response to asynchronous events. It provides a snapshot containing the current connection state, data, and error.
The snapshot’s connectionState tells you if the stream is waiting, active, or done. You can use this to show loading states or a completion message.
Broadcast Streams
By default, streams are single‑subscription – only one listener can listen at a time, and listening again throws an error. Use a broadcast stream when you need multiple listeners. You can create one with StreamController.broadcast() or by calling .asBroadcastStream() on an existing stream.
Common Patterns
- Debouncing: Use
debouncefromrxdartor manually implement usingTimerto wait for a pause in events (e.g., search field).
- Debouncing: Use
- Combining streams: Use
StreamGroup.mergeorrxdartto combine multiple streams into one.
- Combining streams: Use
- Stream transformations: Use
map,where,expand,take,skipto transform the data before it reaches the listener.
- Stream transformations: Use
- Error handling: Use
handleErroror theonErrorcallback to catch errors gracefully.
- Error handling: Use
StreamBuilder vs FutureBuilder
FutureBuilder handles a single asynchronous value (one Future). StreamBuilder handles a sequence of values over time. Use FutureBuilder for one‑time loads (like fetching a user profile), and StreamBuilder for continuous updates (like real‑time chat or live sensor data).
Best Practices
- Always close
StreamControllers indispose()to avoid memory leaks.
- Always close
- Use
StreamBuilderinside the widget tree, not in a separate async method.
- Use
- Provide an
initialDatatoStreamBuilderif you want to show something before the first event.
- Provide an
- For performance, use
constwidgets inside the builder where possible.
- For performance, use
- Prefer
async*generators for simple sequences; useStreamControllerfor manual control.
- Prefer
Common Mistakes
- Not disposing
StreamController: Causes memory leaks and potential crashes.
- Not disposing
- Using a single‑subscription stream with multiple listeners: Throws an error; switch to broadcast.
- Calling
setStateinsideStreamBuilder: Unnecessary; the builder already rebuilds when new data arrives.
- Calling
- Creating the stream inside the build method: Causes the stream to be recreated on every rebuild, which can break subscriptions. Create the stream in
initStateor use a state management solution.
- Creating the stream inside the build method: Causes the stream to be recreated on every rebuild, which can break subscriptions. Create the stream in
Complete Example
Key Takeaways
- Streams represent sequences of asynchronous events.
- Use
StreamControllerto manually add events, andasync*for generators.
- Use
StreamBuilderrebuilds the UI when new data arrives.
- Broadcast streams allow multiple listeners; single‑subscription streams allow only one.
- Always dispose of
StreamControllerto avoid leaks.
- Always dispose of
- Streams are ideal for real‑time updates, user input, and any continuous data flow.