Flutter setState Called After Dispose: How to Fix It
·4 min read·Beginner
An asynchronous operation (a network call, a Future, a Timer, a stream callback) completes and calls setState() after the widget that started it has already been removed from the widget tree.
Error
setState() called after dispose(): _MyWidgetState#a1b2c3(lifecycle state: defunct, not mounted)
androidioswebflutterstate-managementasync
Advertisement
The Problem
An asynchronous operation (a network call, a Future, a Timer, a stream callback) completes and calls setState() after the widget that started it has already been removed from the widget tree.
Symptoms
- •Console exception: 'setState() called after dispose()'
- •Happens intermittently — usually when a user navigates away before an async operation finishes
- •More common on slower network connections where the async gap is longer
Why This Happens
- •An async function awaits a network call, then calls setState() without checking whether the widget is still mounted
- •A Timer or AnimationController keeps running after the widget is disposed because it was never cancelled in dispose()
- •A StreamSubscription's callback fires after the widget is gone because the subscription was never cancelled
- •The user navigated back or the widget was removed from the tree while a Future was still pending
Quick Fix
Check `if (!mounted) return;` immediately after every await and before every setState() call inside an async method.
How to Fix It
1. Guard setState with a mounted check
Future<void> _loadData() async {
final result = await fetchData();
if (!mounted) return; // widget may have been disposed while awaiting
setState(() {
_data = result;
});
}
2. Cancel timers and subscriptions in dispose()
class _MyWidgetState extends State<MyWidget> {
StreamSubscription? _subscription;
Timer? _timer;
@override
void dispose() {
_subscription?.cancel();
_timer?.cancel();
super.dispose();
}
}
3. Re-check mounted after an await before using BuildContext
Future<void> _submit() async {
await someAsyncCall();
if (!context.mounted) return; // Flutter 3.7+ exposes context.mounted directly
Navigator.of(context).pop();
}
Advertisement
Common Mistakes
- ×Checking mounted before the await instead of after it — the widget can be disposed during the await itself
- ×Relying only on the mounted check without cancelling Timers/StreamSubscriptions
- ×Mixing up StatefulWidget's mounted property and context.mounted inconsistently
How to Verify the Fix
- 1.Trigger the async operation, then quickly navigate away before it completes, and confirm no exception is thrown
- 2.Run in debug mode and watch the console during rapid navigation to confirm the warning no longer appears
- 3.Confirm timers/subscriptions are actually being cancelled
Related Problems
Did this article save you time?
I write these for free. If it helped, a coffee keeps me going — and more articles coming.