A Future in Dart is a way to handle a value that will be available later. It represents a task that takes time to finish, like loading data or waiting for a result.
Sometimes, you want to turn this single future value into a stream — a sequence of events that you can listen to. Creating streams from futures helps you work with asynchronous data in a consistent way, especially when you need to connect or combine streams in your program.
Using Stream.fromFuture()
You can create a stream from a single future using Stream.fromFuture()
. This stream will send the future’s result once it completes.
Here’s an example that creates a stream from a future returning a greeting message:
Stream<String> greetStream() => Stream.fromFuture(Future.value('Hello, Dart!'));
void main() {
greetStream().listen((message) {
print(message);
});
}
This stream sends the greeting as a single event when the future finishes.
Using Future.asStream()
Every future has a method called asStream()
that turns the future into a stream. This is another way to create a stream from a single future.
Here’s an example using asStream()
to convert a future greeting into a stream:
void main() {
Future<String> futureGreeting = Future.value('Hello from asStream!');
futureGreeting.asStream().listen((message) {
print(message);
});
}
This stream sends the future’s result as a single event when the future completes.
Listening to a Stream From a Future
Once you create a stream from a future, you can listen to it just like any other stream. When the future completes, the stream sends its result to your listener.
Here’s an example that listens to a stream from a future and prints the greeting message:
Stream<String> greetStream() => Stream.fromFuture(Future.value('Hello, Dart!'));
void main() {
greetStream().listen((message) => print(message));
}
The listener waits for the future to finish, then receives and prints the value from the stream.
Creating Streams from Delayed Futures
You can use Future.delayed
to create a future that completes after some time. Then, use Stream.fromFuture()
to turn it into a stream. This is useful when you want to simulate waiting for something—like a loading screen or a timed message.
Here’s an example that streams a cheer message after a short delay:
Stream<String> delayedCheer() => Stream.fromFuture(
Future.delayed(Duration(seconds: 2), () => 'Go team!'),
);
void main() {
delayedCheer().listen((cheer) {
print(cheer);
});
}
This shows how a future with a delay becomes a stream that sends its result once it’s ready.
Using Futures Returning Custom Objects
You can also create a stream from a future that returns a custom object. This is helpful when working with real-world data like users, animals, or products.
Here’s an example where a future returns an Animal
object, and we stream it using Stream.fromFuture()
:
class Animal {
final String name;
Animal(this.name);
}
Stream<Animal> animalStream() => Stream.fromFuture(
Future.value(Animal('Leo')),
);
void main() {
animalStream().listen((animal) {
print('Animal: ${animal.name}');
});
}
The stream sends the custom object when the future completes, just like it would with a basic value.
Using async*
to Stream From Multiple Futures
When you have more than one future and want to send each result one after the other, use an async*
function with yield
. This lets you build a stream that gives out values as each future completes.
Here’s an example that streams two delayed messages:
Stream<String> multipleMessages() async* {
yield await Future.delayed(Duration(seconds: 1), () => 'Hello');
yield await Future.delayed(Duration(seconds: 1), () => 'World');
}
void main() {
multipleMessages().listen((message) {
print(message);
});
}
This approach gives you more control, letting you send values from multiple futures one at a time.
Converting Future Errors to Stream Errors
If a future throws an error, and you convert it into a stream using Stream.fromFuture()
, the stream will send that error. This helps you handle failures in a consistent stream-based way.
Here’s an example where a future throws an error, and the stream reports it:
Stream<String> errorStream() => Stream.fromFuture(
Future.error('Oops, something went wrong!'),
);
void main() {
errorStream().listen(
(data) => print('Data: $data'),
onError: (err) => print('Error: $err'),
onDone: () => print('Done'),
);
}
This shows how future failures become stream errors that you can catch using the onError
callback.
Conclusion
In Dart, you can easily create streams from futures using Stream.fromFuture()
or Future.asStream()
. Whether the future gives a simple value, a delayed result, or a custom object, you can turn it into a stream and listen to it like any other.
You can also use async*
with yield
to stream values from multiple futures one by one. And if a future throws an error, the stream will pass that error along, allowing you to handle it safely.
These tools help you connect futures and streams smoothly in your Dart programs.