Dart Fundamentals: The Language Behind Flutter · Classes, Modern Dart 3 Features and Async
Async Dart: Future, async/await and Streams
Dart runs on an event loop: a Future is a value that arrives later, async/await makes it read like normal code, Future.wait runs work concurrently, and a Stream delivers many values over time.
What you'll build
A dashboard loader that fetches a user, their orders and notifications (simulated with delays), runs independent requests concurrently, applies a timeout, and listens to a stream of live price updates.
The idea
Dart code runs on a single thread with an event loop (like JavaScript). Slow work (network, files, timers) doesn't block; it returns a Future<T>: a value (or error) that arrives later.
- Mark a function
asyncandawaitfutures inside it; the code reads top to bottom but doesn't block the app. Future.wait([...])(or awaiting a record of futures) runs independent operations concurrently and waits for all of them..timeout(duration)fails a future that takes too long.- A
Stream<T>delivers many values over time (button taps, websocket messages, file chunks). Consume it withawait for, or transform it withmap,where,take. async*functions produce streams withyield.- For CPU-heavy work that would freeze the UI, use isolates (
Isolate.run), Dart's separate workers with their own memory.
The code
import 'dart:async';
Future<String> fetchUser() => Future.delayed(const Duration(milliseconds: 120), () => 'Ana');
Future<int> fetchOrderCount() => Future.delayed(const Duration(milliseconds: 150), () => 3);
Future<List<String>> fetchNotifications() =>
Future.delayed(const Duration(milliseconds: 100), () => ['Your order shipped']);
Future<String> fetchRecommendations() =>
Future.delayed(const Duration(seconds: 2), () => 'too slow to matter');
Stream<double> livePrice(double start) async* {
var price = start;
for (final change in [0.5, -1.25, 2.0, -0.25]) {
await Future.delayed(const Duration(milliseconds: 20));
price += change;
yield price; // each yield sends one value to listeners
}
}
Future<void> main() async {
final watch = Stopwatch()..start();
final user = await fetchUser(); // sequential: wait for each in turn
final orders = await fetchOrderCount();
final sequential = watch.elapsedMilliseconds;
watch.reset();
final (user2, orders2, notes) = await (fetchUser(), fetchOrderCount(), fetchNotifications()).wait;
final concurrent = watch.elapsedMilliseconds;
print('$user has $orders orders; $user2 has $orders2 orders and ${notes.length} notification(s)');
print('concurrent was faster: ${concurrent < sequential}');
try {
await fetchRecommendations().timeout(const Duration(milliseconds: 200));
} on TimeoutException {
print('recommendations timed out: showing the page without them');
}
await for (final price in livePrice(100)) {
print('price: ${price.toStringAsFixed(2)}');
}
final bigMoves = await livePrice(100).where((p) => p > 101).toList();
print('prices above 101: $bigMoves');
}
Output
Ana has 3 orders; Ana has 3 orders and 1 notification(s) concurrent was faster: true recommendations timed out: showing the page without them price: 100.50 price: 99.25 price: 101.25 price: 101.00 prices above 101: [101.25]
Run this on your computer — see “Get Started” for setup.
The timing line compares instead of printing milliseconds, because exact times vary from run to run. The concurrent version takes about as long as the slowest request (150 ms) rather than the sum.
Common mistakes
- Awaiting independent calls one by one, which adds their latencies together; start them together with
Future.waitor a record's.wait. - Forgetting
await, so errors go unhandled and code runs before the result arrives (the analyzer'sunawaited_futureslint helps). - Never cancelling stream subscriptions in long-lived UIs; cancel them in
dispose. - Heavy computation on the main isolate, which freezes Flutter animations; use
Isolate.run. - No timeouts on network calls.
Key points
Future<T>= a value later;async/awaitkeeps code readable.- Run independent futures concurrently (
Future.wait, record.wait); add.timeout. Stream<T>= many values over time;await for,async*/yield.- Isolates for CPU-heavy work.
Further reading: Asynchronous programming: futures, async, await, Streams, Isolates.
Practice questions
How do you run several independent async operations at the same time in Dart?
Answer
Start them all before awaiting, then await them together with Future.wait([...]) or by awaiting a record of futures with .wait, e.g. final (a, b) = await (f1(), f2()).wait;. The total time is about the slowest one, not the sum.
What's the difference between a Future and a Stream?
Answer
A Future delivers a single value (or error) once; a Stream delivers a sequence of values (and errors) over time, consumed with await for or listen.
Open in the app to mark lessons complete, take notes and track your progress.