Introduction
Using a BuildContext after its associated widget has been removed from the tree triggers errors like Looking up a deactivated widget's ancestor is unsafe or Navigator operation requested with a context that does not include a Navigator. This typically happens when an asynchronous operation completes after the user has navigated away from the screen, and the callback attempts to use the context to show a dialog, navigate, or access an inherited widget.
Symptoms
- Console error:
Looking up a deactivated widget's ancestor is unsafe - Error:
Navigator operation requested with a context that does not include a Navigator - ScaffoldMessenger shows:
ScaffoldMessenger requested with a context that does not include a ScaffoldMessenger - App crashes after navigating back quickly from a screen performing async work
Common Causes
setState()called after async operation completes and widget is disposedNavigator.of(context).pop()orpush()after user navigated awayScaffoldMessenger.of(context).showSnackBar()on a disposed widgetProvider.of<T>(context)in a callback after widget removal
Step-by-Step Fix
- 1.**Check
mountedbefore using context in async callbacks**: - 2.```dart
- 3.Future<void> loadData() async {
- 4.try {
- 5.final data = await api.fetchData();
- 6.if (!mounted) return; // Widget was disposed, bail out
- 7.setState(() {
- 8._data = data;
- 9.});
- 10.} catch (e) {
- 11.if (!mounted) return;
- 12.ScaffoldMessenger.of(context).showSnackBar(
- 13.SnackBar(content: Text('Error: $e')),
- 14.);
- 15.}
- 16.}
- 17.
` - 18.**Use
BuildContext.mounted(Flutter 3.7+) in State classes**: - 19.```dart
- 20.class _MyWidgetState extends State<MyWidget> {
- 21.Future<void> _saveData() async {
- 22.final result = await _repository.save(_formData);
- 23.if (!mounted) return;
if (result.isSuccess) { Navigator.of(context).pop(); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(result.error)), ); } } } ```
- 1.**For pre-Flutter 3.7, use the
mountedproperty on State**: - 2.```dart
- 3.class _MyWidgetState extends State<MyWidget> {
- 4.Future<void> _saveData() async {
- 5.final result = await _repository.save(_formData);
- 6.if (!this.mounted) return; // State.mounted, not context.mounted
Navigator.of(context).pop(); } } ```
- 1.Use a cancellation token pattern with http:
- 2.```dart
- 3.class _MyWidgetState extends State<MyWidget> {
- 4.http.Client? _client;
@override void initState() { super.initState(); _client = http.Client(); _loadData(); }
Future<void> _loadData() async { try { final response = await _client!.get( Uri.parse('https://api.example.com/data'), ); if (!mounted) return; // Handle response } catch (e) { if (!mounted) return; // Handle error } }
@override void dispose() { _client?.close(); // Cancel pending requests super.dispose(); } } ```
- 1.For Riverpod or Provider, use autoDispose to clean up:
- 2.```dart
- 3.@riverpod
- 4.Future<Data> loadData(LoadDataRef ref) async {
- 5.ref.onDispose(() {
- 6.// Cleanup when widget is disposed
- 7.});
- 8.return await api.fetchData();
- 9.}
- 10.
`
Prevention
- Always check
mountedbefore using context after anawait - Use
context.mounted(Flutter 3.7+) for cleaner code - Cancel async operations in
dispose() - Prefer
GoRouterwith proper error handling over rawNavigatorcalls - Use state management libraries that handle disposal automatically