A Beginner's Guide to Streams in Flutter
A Beginner’s Guide to Streams in Flutter
Asynchronous programming is at the heart of building responsive and efficient Flutter applications. While Future
is excellent for handling single asynchronous events, Streams are designed to manage a sequence of asynchronous data. In this guide, we’ll dive into what Streams are, how to create them, and how to use the StreamBuilder
widget to listen to a Stream and update your UI in real-time.
🌊 What is a Stream?
A Stream is a sequence of asynchronous events. Think of it like a pipe: you put data in one end, and it comes out the other over time. A Stream can emit three types of events:
- Data Events: The actual data flowing through the stream.
- Error Events: Notifications that an error has occurred.
- Done Events: A signal that the stream has closed and will not emit any more events.
Streams are fundamental to reactive programming in Dart and are used extensively in Flutter for everything from handling user input to managing network responses.
🤔 Why Use Streams?
- Real-time Updates: Perfect for data that changes over time, like live sports scores, stock tickers, or chat messages.
- Handling User Input: Can be used to process continuous user actions, such as text field changes or button presses.
- Decoupling Code: Streams help separate the data source from the UI, leading to cleaner and more maintainable code.
- Composability: Streams can be transformed, filtered, and combined to create complex data flows.
🛠️ Creating a Stream
There are several ways to create a Stream. One of the most common is by using a StreamController
.
import 'dart:async';
// 1. Create a StreamController
final controller = StreamController<int>();
// 2. Get the Stream from the controller
final Stream<int> stream = controller.stream;
// 3. Add data to the stream
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
// 4. Close the stream when done
controller.close();
Another way is to create a Stream using an async*
generator function:
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i; // 'yield' sends a value down the stream
}
}
👂 Listening to a Stream with StreamBuilder
The StreamBuilder
widget is the easiest way to listen to a Stream and rebuild your UI whenever new data arrives.
Example: A Simple Counter App
Let’s build a simple app that displays numbers from a Stream.
a. Create the Stream
We’ll use the countStream
function we defined earlier.
// counter_stream.dart
import 'dart:async';
Stream<int> countStream(int max) async* {
for (int i = 0; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
b. Use StreamBuilder
in the UI
// main.dart
import 'package:flutter/material.dart';
import 'counter_stream.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter StreamBuilder Guide')),
body: Center(
child: StreamBuilder<int>(
stream: countStream(10), // The stream to listen to
builder: (context, snapshot) {
// 1. Check for connection state
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// 2. Check for errors
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
// 3. Check for data
if (snapshot.hasData) {
return Text(
'Counter: ${snapshot.data}',
style: TextStyle(fontSize: 48),
);
}
// 4. Handle the 'done' state
return Text('Stream has finished.', style: TextStyle(fontSize: 24));
},
),
),
);
}
}
In this example:
StreamBuilder
subscribes tocountStream(10)
.- The
builder
function is called for every new event from the stream. snapshot
contains the latest information about the stream’s state (connectionState
,hasData
,hasError
,data
).- The UI updates automatically, showing a loading indicator, then the counter value, and finally a completion message.
🎯 Summary
- Streams are for handling sequences of asynchronous data.
- A
StreamController
is a common way to create and manage a stream. - The
StreamBuilder
widget is a powerful tool for building reactive UIs in Flutter that listen to streams. - Always check the
connectionState
,hasError
, andhasData
properties of theAsyncSnapshot
for robust UI.
📚 Resources
Happy coding! 🚀