How to use async/await in 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.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
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.
- Turn any function into an
async
function. - 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
- Asynchronous function is depicted with a return type of Future, not async.
- 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.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Summary of asynchronous programming in Dart
Here is my summary of asynchronous programming in Dart.
- Asynchronous function is a function that returns the type of Future.
- We put
await
in front of an asynchronous function to make the subsequence lines waiting for that future's result. - We put
async
before the function body to mark that the function supportawait
. - 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 ShareSwift 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.
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.