Flutter setState Called After Dispose: How to Fix It
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.
setState() called after dispose(): _MyWidgetState#a1b2c3(lifecycle state: defunct, not mounted)
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.
This is one of those exceptions every Flutter developer hits eventually, usually the first time they ship a screen with a network call and a "back" button on it. The bug isn't really about setState(). It's about timing: your widget tree and your async code run on different clocks, and Flutter has no way to pause a Future just because the user tapped back.
The fix pattern is the same every time: check mounted right before you touch the widget's state, not before you start the async work. A lot of developers check mounted at the top of the function and assume that's enough. It isn't. The widget can be disposed at any point during the await, so the check only means something if it happens immediately before the setState() call itself.
If you're seeing this a lot across a codebase, it's usually a sign that timers and stream subscriptions aren't being cancelled in dispose() consistently. That's worth fixing at the pattern level (a base class, a mixin, or just a team convention) rather than patching each screen one at a time as the crashes come in.
How to Fix It
1. Guard setState with a mounted check
2. Cancel timers and subscriptions in dispose()
3. Re-check mounted after an await before using BuildContext
// 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
Skip the boilerplate
Production-ready Flutter starter kit with Firebase Auth, Firestore, Cloud Functions, push notifications, and Clean Architecture — ship your app in days, not months.
Did this article save you time?
I write these for free. If it helped, a coffee keeps me going — and more articles coming.