What is StreamBuilder in Flutter?
StreamBuilder is a widget that listens to a Stream and rebuilds its UI whenever a new event is emitted. It's perfect for handling real‑time data like live chats, location updates, Firebase snapshots, or any continuous flow of data. StreamBuilder works similarly to FutureBuilder, but for multiple asynchronous events over time.
Basic Usage
To use StreamBuilder, you provide a stream and a builder function. The builder is called with an AsyncSnapshot each time the stream emits data, completes, or throws an error.
Understanding AsyncSnapshot with Streams
The AsyncSnapshot for a stream has the same properties as for a future, but the connectionState can be more nuanced:
- connectionState:
ConnectionState.none: No stream provided yet.ConnectionState.waiting: Stream is established but no data received yet.ConnectionState.active: Stream is active and data is arriving.ConnectionState.done: Stream has completed (closed).
- hasData: Whether the snapshot contains non‑null data (the latest emitted value).
- data: The latest value emitted by the stream.
- hasError: Whether the stream has emitted an error.
- error: The error object (if any).
Building UI for Different States
A typical pattern handles waiting, active, error, and done states. Here's a complete example:
Note: For streams that never complete (like a continuous timer), connectionState will remain ConnectionState.active forever. You should rely on hasData to check if a value is available.
Real‑time Example: A Counter Stream
Let's create a simple stream that emits an incrementing number every second and use StreamBuilder to display it.
Important: Where to Create the Stream
Just like with FutureBuilder, you should never create the stream directly inside the build method. That would cause a new stream to be created on every rebuild, breaking the connection and causing memory leaks. Instead, create the stream in initState (for a stateful widget) or keep it as a final variable in a provider / repository.
Handling Stream Errors and Completion
Streams can emit errors or complete. Use snapshot.hasError to show an error widget, and you can detect completion by checking snapshot.connectionState == ConnectionState.done. For example, you might show a message when a live stream ends.
Common Mistakes Beginners Make
- Creating the stream inside
build: Leads to stream re‑creation, losing previous events and causing memory leaks. - Not handling
hasDatacorrectly: For streams that may not emit data immediately, always checksnapshot.hasDatabefore accessingsnapshot.data. - Forgetting to cancel streams: In a real app, you should cancel the stream subscription when the widget is disposed (e.g., using
StreamControllerand disposing indispose). - Misunderstanding
connectionState: For infinite streams,connectionStateis alwaysConnectionState.active. Don't wait fordoneto show data. - Assuming the stream will provide data immediately: Show a loading indicator until
hasDatabecomes true. - Not handling errors: Always include an error case to avoid crashing the app.
Key Points to Remember
- StreamBuilder listens to a stream and rebuilds on every new event.
- Use
AsyncSnapshotproperties:connectionState,hasData,data,hasError,error. - For infinite streams, check
hasDatarather than waiting forConnectionState.done. - Create the stream once (e.g., in
initState) and reuse it. - Always handle errors and loading states for a robust UI.
- Remember to cancel stream subscriptions in
disposeif you manage streams manually (e.g., withStreamController).
Common Interview Questions
- How does StreamBuilder differ from FutureBuilder?
- What are the possible values of
ConnectionStateand what do they mean for a stream? - Why should you avoid creating a stream inside the
buildmethod? - How would you handle a stream that might not emit data immediately?
- How do you cancel a stream subscription when using StreamBuilder?
- Can StreamBuilder be used with a broadcast stream? What's the difference?
- How do you show a loading indicator while waiting for the first data from a stream?