Dart: Futures and Streams
Learn how to use Futures and Streams for writing asynchronous code in dart By Michael Katz.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Dart: Futures and Streams
30 mins
Dart, like many other programming languages, has built-in support for asynchronous programming — writing code that runs later. Your program can wait for work to finish and perform other tasks while waiting. This technique is often used to fetch information from a device or server and not block your program.
In this tutorial, you’ll learn how to use Futures and Streams for writing asynchronous code in Dart.
Getting Started
This tutorial is a pure Dart experience so use DartPad at https://dartpad.dev/. DartPad lets you edit and run Dart code in the browser. There is no corresponding Flutter app, but all the concepts and code can be used in a normal Flutter app.
For this tutorial, you’ll simulate an API that returns demographic data on world cities. The program will “load” data from this simulated API as if you were building a mapping or educational app. In reality, the data is all hard-coded and sent after a time delay.
In DartPad, create a new Dart pad.
You’ll see a basic main
function that prints a count.
Async Basics
Synchronous code has limits. You can only run one chunk of code at a time, which might be problematic if that code takes a long time to run. Asynchronous code lets you write code that responds to events that happen later, such as data becoming available from an API.
For the cities program, display a greeting and load some cities.
Replace the DartPad contents with:
List<String> fetchCityList() {
print("[SIMULATED NETWORK I/O]");
return ['Bangkok', 'Beijing', 'Cairo', 'Delhi', 'Guangzhou', 'Jakarta', 'Kolkāta', 'Manila', 'Mexico City', 'Moscow', 'Mumbai', 'New York', 'São Paulo', 'Seoul', 'Shanghai', 'Tokyo'];
}
void printCities(List<String> cities) {
print("Cities:");
for (final city in cities) {
print(" " + city);
}
}
void main() {
print("Welcome to the Cities program!");
final cities = fetchCityList();
printCities(cities);
}
This has two helper functions: fetchCityList
returns a list of cities, and printCities
prints that list to the console. The main
function displays the greeting and uses the helper method to fetch the cities. For a Flutter app, you might display the city list using a ListView widget.
The fetchCityList
function is meant to simulate loading data from the network, but it finishes immediately, which isn’t how an API call normally works.
What if that fetch was slow or error-prone?
Replace the existing main
function with the following (try not to read into it yet):
Future<List<String>> fetchSlowCityList() async {
print("Loading...");
await Future.delayed(Duration(seconds: 2));
return fetchCityList();
}
void main() async {
print("Welcome to the Cities program!");
final cities = await fetchSlowCityList();
printCities(cities);
}
Run the code and you’ll feel the delay in the console. The 2-second delay is helpful because you don’t know your end-users’ networking conditions.
If you were to run the above code on the main thread of a Flutter app, your app would be unresponsive while it waits for completion.
Using Futures
Now that you’ve seen the Dart type Future
in action, let’s learn how to use it in your own code.
A Future is an object that will return a value (or an error) in the future. It’s critical to understand the Future
itself is not the value, but rather the promise of a value.
To see this in practice, replace main
with the following:
void main() {
print("Welcome to the Cities program!");
// 1
final future = Future.delayed(
const Duration(seconds: 3),
fetchCityList,
);
// 2
print("The future object is actually: " + future.toString());
// 3
future.then((cities) {
printCities(cities);
});
// 4
print("This happens before the future completes.");
}
This example highlights some of the properties and conditions of using futures.
- A
Future
can be built a few ways. Adelayed
future will execute a provided computation after a specified delay. In this case, the delay is a 3-secondDuration
, and the computation is a call tofetchCityList
. - This
print
statement is a reminder that aFuture
is its own type and isn’t the value of the computation. - The
then
function will execute a callback with a computed value when the future is complete. In this case, the value completed value is thecities
variable, and it is then forwarded to the print-out function. - This
print
statement is a reminder that even though this appears after the call tothen
in program order. It’ll execute before the completion callback.
If you run the program, you’ll see the output shows the order in which the code is executed.
Welcome to the Cities program!
The future object is actually: Instance of '_Future<List<String>>'
This happens before the future completes.
[SIMULATED NETWORK I/O]
Cities:
Bangkok
Beijing
Cairo
Delhi
Guangzhou
Jakarta
Kolkāta
Manila
Mexico City
Moscow
Mumbai
New York
São Paulo
Seoul
Shanghai
Tokyo
Future States
A Future
has two states: uncompleted and completed. An uncompleted Future
is one that hasn’t produced a value (or error) yet. A completed Future
is a Future
after computing its value.
In this next example, you’ll use a Timer
to show a loading indicator text in the console. At the top of the DartPad, add:
import 'dart:async';
This will import the async
package so you can use a timer. Next, replace main
with:
void main() {
// 1
final future = fetchSlowCityList();
// 2
final loadingIndicator = Timer.periodic(
const Duration(milliseconds: 250),
(timer) => print("."));
// 3
future.whenComplete(() => loadingIndicator.cancel());
// 4
future.then((cities) {
printCities(cities);
});
}
This code illustrates the completed state by assigning a whenComplete
callback. This code does the following:
- Reuses the helper function to create a city list
Future
. - The timer will drop a “.” in the console every 250 milliseconds.
- When the loading future is complete, it cancels the timer so it stops printing dots.
- When the future completes, print the completed value to the console.
whenComplete
is called when the future finishes, regardless of whether it produced a value or an error. It’s a good place to clean up state, such as canceling a timer. On the other hand, then
is only called when a value is present.