iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Future / async-await

Future in Dart is the equivalent of a Promise: a placeholder for a value that will arrive later. async/await syntax is the same as in JavaScript or C#. The differences worth knowing: Future.error vs throw, the FutureBuilder widget for declarative UI, and the static helpers (wait, any, delayed) that handle composition.

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'; }

Discussion

Loading…