Bidev

Flutter setState Called After Dispose: How to Fix It

4 min readBeginner

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

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

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(); }

// 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. 1.Trigger the async operation, then quickly navigate away before it completes, and confirm no exception is thrown
  2. 2.Run in debug mode and watch the console during rapid navigation to confirm the warning no longer appears
  3. 3.Confirm timers/subscriptions are actually being cancelled
Share this

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.

Get Flutter Firebase Kit — $5

Did this article save you time?

I write these for free. If it helped, a coffee keeps me going — and more articles coming.

Buy me a coffee

Comments

Comments

Leave a comment

0/2000

Comments appear after review.