Bidev

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. 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

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.

Buy me a coffee

Comments

Comments

Leave a comment

0/2000

Comments appear after review.