Future / async-await
Future
Async functions, FutureBuilder, error and timeout patterns
EXAMPLE
import 'dart:async';
import 'package:flutter/material.dart';
// 1) An async function that returns a Future<T>
Future<String> fetchUserName(String id) async {
await Future.delayed(const Duration(milliseconds: 300));
if (id.isEmpty) {
throw ArgumentError('id is required');
}
return 'Alice (id=$id)';
}
// 2) Future composition: WhenAll-style with Future.wait
Future<Map<String, dynamic>> loadDashboard() async {
final results = await Future.wait([
fetchUserName('42'),
Future<int>.delayed(const Duration(milliseconds: 200), () => 17),
Future<double>.delayed(const Duration(milliseconds: 100), () => 99.5),
]);
return { 'name': results[0], 'orders': results[1], 'score': results[2] };
}
// 3) Timeouts and error mapping
Future<T> withTimeout<T>(Future<T> f, Duration d) async {
try {
return await f.timeout(d);
} on TimeoutException {
throw 'request timed out after ${d.inSeconds}s';
}
}
// 4) FutureBuilder — declarative state machine: loading / data / error
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder<Map<String, dynamic>>(
future: loadDashboard(),
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) {
return Center(child: Text('Error: ${snap.error}'));
}
final d = snap.data!;
return Column(children: [
Text('Welcome ${d['name']}'),
Text('You have ${d['orders']} orders'),
Text('Score ${d['score']}'),
]);
},
);
}
}
// 5) Re-runs: FutureBuilder calls the future on EVERY rebuild.
// Always hoist the Future into state, otherwise you re-fetch on every setState.
class StablePage extends StatefulWidget {
const StablePage({super.key});
@override
State<StablePage> createState() => _StablePageState();
}
class _StablePageState extends State<StablePage> {
late final Future<Map<String, dynamic>> _future;
@override
void initState() { super.initState(); _future = loadDashboard(); }
@override
Widget build(BuildContext c) => FutureBuilder(future: _future, builder: (_, __) => const SizedBox());
}
Why it matters
The most common Flutter performance bug: putting `future: loadX()` directly in `FutureBuilder` inside `build()`. Every parent rebuild triggers a fresh fetch. Hoist the Future into a State field (`late final` or initialised in `initState`) so the same Future survives rebuilds.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
Future<String> fetch() async {
final r = await http.get(Uri.parse('https://api.example.com'));
return r.body;
}
Try it Yourself »
Exercise
Mark a fn that awaits something.
Future<String> fetch()
{ return 'ok'; }
Five letters.
Discussion
Loading…