Adding async to a function makes asynchronous code easier to read, but it does not decide which result your interface should display. Search is a good example: a request for “fl” can finish after a newer request for “flutter” and replace the correct results.
Model the lifetime of a request
A Future represents one eventual result or error. Use await when later work depends on that result, and handle failures where you can make a meaningful decision. An empty catch block turns a useful failure into an invisible bug. The loading state also needs an owner so an older request cannot clear a newer request's spinner.
int requestVersion = 0;
Future<void> search(String query) async {
final version = ++requestVersion;
try {
final results = await repository.search(query);
if (version != requestVersion) return;
showResults(results);
} catch (error) {
if (version != requestVersion) return;
showRecoverableError();
}
}
This presentation-layer sketch ignores obsolete responses; it does not cancel network work. repository and the display methods belong to your application. Keep the version on a controller instance, not in a global shared by unrelated screens. Where your networking library supports cancellation, combine it with result ownership.
Respect the screen lifecycle
After an asynchronous gap, a widget may already be gone. Check context.mounted before using that context for navigation or a snackbar. A mounted check protects context access; it does not solve overlapping requests. Controllers should also release subscriptions and invalidate outstanding work when disposed.
Choose parallel work deliberately
Independent requests can start together. Dependent requests must wait for their prerequisites. If one panel may succeed while another fails, decide whether each needs an independent error state instead of treating the whole screen as a single all-or-nothing operation.
Test the order you hope never happens
- Complete the second search before the first.
- Navigate away while a request is pending.
- Fail a refresh while cached results remain visible.
- Tap retry twice and confirm only the intended result wins.
Official references: Dart asynchronous programming and BuildContext.mounted.
