Flutter Async Gap Context Skill
Never use BuildContext after an await without checking mounted first.
When to apply
- Widget code uses
BuildContextafter anawait - User gets "Looking up a deactivated widget's ancestor" error
- Code review finds
Navigator.of(context)after async operation ScaffoldMessenger.of(context)used after await
The problem
When a widget is unmounted during an async operation, its BuildContext becomes invalid:
// CRASH — context may be invalid after await
Future<void> _submit() async {
await repository.save(data);
Navigator.of(context).pop(); // BOOM if widget unmounted
ScaffoldMessenger.of(context) // BOOM
.showSnackBar(SnackBar(content: Text('Saved')));
}
The fix: mounted guard
// SAFE — check mounted after every await
Future<void> _submit() async {
await repository.save(data);
if (!context.mounted) return; // guard BEFORE using context
Navigator.of(context).pop();
if (!context.mounted) return; // guard again if more awaits follow
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Saved')));
}
Rules
- After EVERY
await, checkcontext.mountedbefore usingcontext - One
mountedcheck per async gap — if there are multiple awaits, check after each context.mountedis available in Flutter 3.7+ (usemountedon State for older)- In
StatefulWidget, useif (!mounted) return;(State's property) - In
StatelessWidgetcallbacks, capture context before the gap:
// StatelessWidget pattern
ElevatedButton(
onPressed: () async {
final nav = Navigator.of(context); // capture BEFORE await
final messenger = ScaffoldMessenger.of(context);
await repository.save(data);
nav.pop(); // safe — captured before await
messenger.showSnackBar(...); // safe
},
)
Common patterns
showDialog after async
Future<void> _confirmDelete() async {
final confirmed = await showDialog<bool>(...);
if (confirmed != true) return;
await repository.delete(item);
if (!context.mounted) return; // REQUIRED
Navigator.of(context).pop();
}
Multiple sequential awaits
Future<void> _process() async {
final result = await step1();
if (!context.mounted) return;
await step2(result);
if (!context.mounted) return;
Navigator.of(context).pushReplacement(...);
}
GoRouter navigation
Future<void> _login() async {
await authService.login(email, password);
if (!context.mounted) return;
context.go('/home'); // GoRouter also needs mounted check
}
Checklist
- Every
awaitfollowed bycontextuse has amountedcheck between them -
Navigator.of(context)after async has guard -
ScaffoldMessenger.of(context)after async has guard -
context.go()/context.push()(GoRouter) after async has guard -
Theme.of(context)after async has guard -
showDialog/showModalBottomSheetafter async has guard
Source: thawzintoe-ptut/Ptut-claude — distributed by TomeVault.