There are multiple ways to create a Stream in Dart, depending on the type of data flow and how you want to emit values. The most common methods include using:
StreamControllerasync*andyieldsyntax- Predefined constructors like
Stream.fromIterable()orStream.periodic()
Let me explain each briefly with examples and when I’ve used them in real projects.
1. Using StreamController (Manual control over stream events)
This is the most flexible way — you can add data, errors, or close the stream manually.
import 'dart:async';
void main() {
final controller = StreamController();
// Listen to the stream
controller.stream.listen(
(data) => print('Received: $data'),
onError: (error) => print('Error: $error'),
onDone: () => print('Stream closed'),
);
// Add data to the stream
controller.add(10);
controller.add(20);
controller.addError('Something went wrong');
controller.close();
}
In this example, the StreamController gives you full control — you decide when to emit data or close the stream.
I’ve used this in a Flutter app where I needed to broadcast UI state changes manually (for example, in a custom BLoC pattern before migrating to flutter_bloc).
Challenge: Managing the lifecycle manually — if you forget to close the controller, it may cause memory leaks.
Best practice: Always close the controller in the dispose() method or use StreamController.broadcast() for multiple listeners.
2. Using async and yield (Easiest way to generate a stream)*
Dart provides a special syntax for generating streams using an asynchronous generator function.
Stream numberStream() async* {
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i; // emits a new value each time
}
}
void main() async {
await for (var value in numberStream()) {
print('Received: $value');
}
}
Here, every second a new number is emitted.
I used this approach in a countdown timer feature for a quiz app. It was clean and didn’t require manual stream management.
Challenge: You can’t easily push values dynamically once the function has started — it’s great for sequential or predictable streams.
Alternative: For dynamic streams, I use StreamController instead.
3. Using Built-in Constructors
Dart also provides quick utility constructors for simple use cases:
Stream.fromIterable([1, 2, 3])→ emits list items one by one.Stream.periodic(Duration(seconds: 1), (count) => count)→ emits values periodically.
Example:
void main() {
Stream.periodic(Duration(seconds: 1), (count) => count + 1)
.take(5)
.listen((value) => print('Tick: $value'));
}
I used Stream.periodic to show a real-time clock in one of my Flutter dashboards — it updates the time every second automatically.
So overall, the method you choose depends on your use case:
- Use
StreamControllerfor dynamic or event-driven data. - Use
async*andyieldfor predictable sequential events. - Use built-in constructors for simple timed or list-based streams.
Streams are powerful in Dart — once you understand how to create and manage them properly, they become a backbone for building reactive, real-time features efficiently.
