The asynchronicity is the ability to wait for slow operations without blocking the rest of the program.
So far, all our code executed immediately: we performed a sum, and the result was there instantly. But in the real world, especially in mobile app development, things take time.
Reading a file, querying a database, or requesting information from the internet is not instantaneous. If our code “freezes” waiting for the internet to respond, the application will hang, and the user will think it has crashed.
To avoid this, Dart uses Asynchronicity. Today, we’ll master time in our code.
Dart, isolates, and the event loop
To understand asynchronicity, we first need to understand how Dart works internally.
Dart code runs inside isolates. Each isolate has its own memory and processes Dart code on a single thread via an event loop. An application can create more isolates for parallel work, but typical asynchronicity doesn’t require that.
So, how is it possible to download an image while the user scrolls? Thanks to the event loop, which allows the program to continue processing events while an external operation finishes.
Imagine you are a waiter (the Dart thread):
A customer asks you for a coffee (Asynchronous operation).
You note the order, pass it to the kitchen, and continue serving other customers (keep rendering the screen).
When the kitchen finishes the coffee, it notifies you (Event).
You serve the coffee when you have a free moment.
If you stood at the counter waiting for the coffee to be made, the restaurant would block. That’s asynchronicity: starting a task and promising to handle the result when it’s ready.
The Future object
In Dart, that “promise” that something will happen in the future is represented by the Future object.
A Future is like a closed box.
- Initially, it’s pending (the box is closed).
- After some time, it completes (the box opens).
- It can complete with a value (success).
- Or complete with an error (failure).
The data type syntax is Future<DataType>. For example, Future<String> means “I promise there will be text here, but later.”
Simulating a wait
We’ll use Future.delayed to simulate downloading data from the internet.
void main() {
print('Program start');
// Simulate a request that takes 2 seconds
httpGet('https://api.luisllamas.es/data');
print('Program end');
}
Future<String> httpGet(String url) {
return Future.delayed(Duration(seconds: 2), () {
return 'API response';
});
}If you run this, you’ll see something curious in the console:
Program startProgram end- (2 seconds later)… (We don’t print anything yet, but the task finishes).
Dart didn’t wait! It launched the request and continued executing print('Program end').
async and await
In the old days, to handle the Future result we used .then(), which led to hard-to-read nested code (“Callback Hell”).
Modern Dart introduced the keywords async and await, which allow us to write asynchronous code as if it were synchronous (linear).
async
It transforms a normal function into an asynchronous function. It forces the function to return a Future.
await
It tells Dart: “Wait here (without blocking the App) until this Future completes and give me its value.”
Basic rule: You can only use await inside a function marked as async.
Let’s fix the previous example:
// 1. Mark main as async
Future<void> main() async {
print('Program start');
// 2. Use await to wait for the result
String data = await httpGet('https://api.luisllamas.es/data');
// This line does NOT execute until httpGet finishes
print(data);
print('Program end');
}
Future<String> httpGet(String url) {
return Future.delayed(Duration(seconds: 2), () {
return 'Hello World - Data received';
});
}Output with await:
Program start- (waits 2 seconds…)
Hello World - Data receivedProgram end
Now we’ve managed to control the flow of time!
Error handling with try-catch
What happens if the request fails (no internet)? The Future will complete with an error, and if we don’t handle it, our App will crash (Unhandled Exception).
With async/await, error handling is as simple as using a try-catch block.
Future<void> main() async {
print('Connecting...');
try {
String data = await simulateError();
print(data);
} catch (error) {
print('Oops! An error occurred: $error');
}
}
Future<String> simulateError() {
return Future.delayed(Duration(seconds: 1), () {
throw 'Server not found'; // Throw an exception
});
}A common mistake: forgetting await
A typical beginner mistake in Flutter is calling an asynchronous function without using await when you actually need the data.
Future<void> loadUser() async {
var user = getUserFromDatabase(); // ⚠️ Missing await
// Here, 'user' is not the data, it's the entire Future object!
// print(user.name); // Error: The Future object has no property 'name'
}If you forget await, you get the “box” (the Future), not the content of the box.