How to use async/await in Flutter

⋅ 7 min read ⋅ Flutter

Table of Contents

Recently I had an opportunity to work on a Flutter application using the Dart programming language.

One of the most confusing things in Dart is asynchronous programming. In Swift, we only have async and await, but in Dart (and probably other languages), we also have a Future (or Promise) object.

A combination of a Future, async, and await is quite error-prone and confusing for me.

In this article, I will show you a series of asynchronous implementations, highlight pitfalls and give a usage summary of the three components (Future, async, and await) at the end.

What is Future

A Future is an object that represents the result of an asynchronous operation and can have two states: uncompleted or completed.

This is the indicator that we use to identify asynchronous operations in Dart. An asynchronous function is a function that returns Future.

Future<String> getWeatherForecast() {
return Future.delayed(Duration(seconds: 2), () => "Partly cloudy");
}

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

async and await

async and await are keywords that provide a way to make asynchronous operations appear synchronous.

To understand this, let's see how we deal with asynchronous operation results with a callback.

We use a then method to handle the future value, which accepts a callback parameter that gets called when the future completes.

In the following example, we get forecase result and print the value out in the callback.

Future<String> getWeatherForecast() {
return Future.delayed(Duration(seconds: 2), () => "Partly cloudy");
}

void fetchWeatherForecast() {
print("start: fetchWeatherForecast");
final forecast = getWeatherForecast();
forecast.then(
(value) => print("fetchWeatherForecast: $value"),
);
print("end: fetchWeatherForecast");
}

void main(List<String> arguments) {
print('start: main');
fetchWeatherForecast();
print('end: main');
}

Here is the result:

start: main
start: fetchWeatherForecast
end: fetchWeatherForecast
end: main
// Wait for 2 seconds
fetchWeatherForecast: Partly cloudy

You can see that the code is quite hard to predict. Every print statement shows up not in the order it was defined.

Let's see how async and await can help us on this.

What is async

async has only two functions.

  1. Turn any function into an async function.
  2. Automatically wrap return statement in Future.

You can declare an async function by adding an async keyword before the function body.

Future<int> futureInt() async {
// 1
return 1;
}

1 We don't need to explicitly return Future.value(1) here since async does the wrapping.

If you try to remove the async keyword, you will get the following error.

A value of type 'int' can't be returned from the function 'futureInt' because it has a return type of 'Future'

Future<int> futureInt() {
// // A value of type 'int' can't be returned from the function 'futureInt' because it has a return type of 'Future<int>'
return 1;
}

Caveat

Please note that async function is different meaning from asynchronous function that we talked earlier.

You can declare a synchronous function with async without an error.

void noFuture() async {
// ...
}

An async keyword would try to help you turn your function into asynchronous most of the time by enforcing the return type of your function to Future.

You will get the following error if the return type is not Future.

Functions marked 'async' must have a return type assignable to 'Future'.
Try fixing the return type of the function, or removing the modifier 'async' from the function body.

int futureInt() async {
return 1;
}

But if the return type is void, the async keyword won't save you from that.

The following code works without any error.

void noFuture() async {
//
}

The above code would be treated as a synchronous function even with the async keyword. I don't know if this is a bug or if there is any wisdom behind it, but I think you should be aware of this.

In summary, the async keyword doesn't mean an asynchronous function.

What is await

You can think of await as a syntactic sugar of then. It makes asynchronous operations look synchronous.

await will wait for a future to complete before executing the subsequence statement. This makes asynchronous operations appear to be synchronous.

Let's modify fetchWeatherForecast to use `async/await.

Here is our current implementation.

void fetchWeatherForecast() {
print("start: fetchWeatherForecast");
final forecast = getWeatherForecast();
forecast.then(
(value) => print("fetchWeatherForecast: $value"),
);
print("end: fetchWeatherForecast");
}


void main(List<String> arguments) {
print('start: main');
fetchWeatherForecast();
print('end: main');

Here is the implementation with async/await.

// 1
Future<void> fetchWeatherForecast() async {
print("start: fetchWeatherForecast");
// 2
final forecast = await getWeatherForecast();
// 3
print("fetchWeatherForecast: $forecast");
print("end: fetchWeatherForecast");
}


void main(List<String> arguments) {
print('start: main');
fetchWeatherForecast();
print('end: main');

1 We make fetchWeatherForecast support await by adding the async keyword.
2 The we wait for the result from getWeatherForecast() by using await.
3 This line won't executed until we get a result from getWeatherForecast().

Here is the result:

// Use then
start: main
start: fetchWeatherForecast
end: fetchWeatherForecast
end: main
// Wait for 2 seconds
fetchWeatherForecast: Partly cloudy

// Use async/await
start: main
start: fetchWeatherForecast
end: main
// 2 seconds
fetchWeatherForecast: Partly cloudy
end: fetchWeatherForecast

As you can see, both fetchWeatherForecast: Partly cloudy and end: fetchWeatherForecast print after final forecast = await getWeatherForecast(); is finished.

The output of the two methods is different. The then method prints end: fetchWeatherForecast immediately after start: fetchWeatherForecast.

Since async/await is just syntactic sugar, we can modify it a bit to give the same output result.

Future<void> fetchWeatherForecast() async {
final forecast = getWeatherForecast();
print("start: fetchWeatherForecast");
forecast.then(
(value) {
print("fetchWeatherForecast: $value");
// 1
print("end: fetchWeatherForecast");
},
);
}

1 We move print("end: fetchWeatherForecast") into the success callback.

You can think of any statements after await as statements within a callback in then.

Caveats

  1. Asynchronous function is depicted with a return type of Future, not async.
  2. Nothing stops you from using an asynchronous function like a synchronous function, so you have to be careful when using them.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Summary of asynchronous programming in Dart

Here is my summary of asynchronous programming in Dart.

  1. Asynchronous function is a function that returns the type of Future.
  2. We put await in front of an asynchronous function to make the subsequence lines waiting for that future's result.
  3. We put async before the function body to mark that the function support await.
  4. An async function will automatically wrap the return value in Future if it doesn't already.

I think that's all you need to know about asynchronous programming in Dart.


Read more article about Flutter or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
Swift Type placeholder: What is it and when to use it

Type placeholders allow us to write a type placeholder (_) in a place where type is expected. A compiler will automatically infer the type of that placeholder. But what is the benefit of it? Let's find out.

Next
How to handle API Changes with #available

Every year Apple introduces new features to the system, and sometimes they have to deprecate some old APIs to make room for the new ones. Change is an inevitable thing in programming. Let's learn how to handle the changes.

← Home