Futures, async, await: Threading in Flutter

Rap Payne
Rap Payne
Jun 8 · 5 min read
Photo by Franck V. on Unsplash

If F=D and D=ST, then F=ST, right?

Flutter is written using Dart and Dart is a single-threaded language then Flutter apps are single-threaded. This means that a Flutter app can only do one thing at a time.

That is all true. But that does not mean that Flutter apps are forced to wait for slower processes.

Flutter apps use an event loop

This should come as no surprise since Android has a main looper and iOS has a run loop (aka. main loop). Heck, even JavaScript devs are unimpressed since JavaScript itself has a … wait for it … event loop. Yes, all the cool kids are using an event loop these days.

An event loop is a background infinite loop which periodically wakes up and looks in the event queue for any tasks that need to run. If any exist, the event loops puts them onto the run stack if and only if the CPU is idle.

As your app is running instructions, they run serially — one after another. If an instruction is encountered that may potentially block the main thread waiting on some resource, it is started and the ‘wait’ part is put on a separate queue.

Why would it wait?

Certain things are slow compared to the CPU. Reading from a file is slow. Writing to a file is even slower. Communicating via Ajax? Forget about it. If we kept the waiting activity on the main thread it would block all other commands. What a waste!

The way this is handled in JavaScript, iOS, Android, and now Dart is this:

  1. An activity that is well-known to be slow is started up as normal.

All you do is write the code to create the future and to handle futures that are returned from other methods.

In Dart you have the ability to specify the type of thing that Future will give you eventually:

When we have that Future object, you may not have the data, but you definitely have a promise to get that data in the Future. (See what they did there?)

How do we get the data from a Future?

You tell the Future what to do once the data is ready. Basically, you’re responding to a “Yo, the data is ready” event and telling the Future what to do by registering a function which we refer to as a callback.

myFuture.then(myCallback);

The .then() function is how you register that callback function. The callback should be written to handle the promised data. For example, if we have a Future<Foo> then our callback should have this signature:

So if the Future will return a Person, your callback should receive a Person. If the Future promises a String, your callback should receive a String. And so forth.

Your callbacks should always return void because there’s no way that the .then function can receive a returned value. This makes a ton of sense when you think about it because remember that it is no longer running within the main thread of your app so it has no way of merging back in. So how do you get a value from the callback? Several methods, but the most understandable is that you use a variable that is defined outside the callback:

To get a value out of an async callback, you set an external variable

Tacking a .then() onto your Future object can occasionally muddy up your code. If you prefer, you can clean it up a bit with await.

await

There’s another way to get the data which is more straightforward to read. Instead of using .then(), you can await the data.

Foo theIncomingData = await somethingThatReturnsAFuture();

Awaiting pauses the running code to … well … wait for the Future to resolve before it moves on to the next line. In the example above, the “Foo” that you’re awaiting is returned and put into theIncomingData. Simple.

Or maybe it isn’t that simple…

async

Like it or not, when you use await inside a function, that function is now in danger of blocking the main thread, so it must be marked as async. For example, this function …

… becomes this when we await:

Note that when we added an await on line 2, we must mark the function itself with async. The subtle thing is that when it is marked as async, anything returned from that function is immediately wrapped in a Future unless it is already one.

Are you sitting down? Check this out: whenever you choose to await a future the function must be marked as async and therefore all who call it must be awaited and they must be marked as async and so on and so on. Eventually you get to a high enough spot in the call chain that you’re not in a function so you don’t have to mark it as async.

Maybe I spoke too soon when I said this is simpler.

Hint: The Flutter build() method cannot be async but events like onPress can. So try to steer your async activities into events to solve this recursive async-await-async-await thing.


Here are your takeaways:

  1. Futures allow your Dart code to be asynchronous — it can handle slow-running processes in a separate thread (kind of).

If you’d like to do some more reading on Futures, here’s a thorough coverage from the Dart team: https://www.dartlang.org/tutorials/language/futures

You can clap up to 50 times per article. Thanks for your support!

(Did you know that you can clap up to 50 times per article? Help me out by hitting the clap button a bunch of times if this has been interesting or helpful to you. Thanks very much!)

Flutter Community

Articles and Stories from the Flutter Community

Rap Payne

Written by

Rap Payne

Twitter: @Rap_Payne. Mobile and Web app developer. Consultant, trainer, author. Writing “Beginning App Development with Flutter” for apress.com.

Flutter Community

Articles and Stories from the Flutter Community

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade