Skip to content

Flutter errorFlutter: “setState() called after dispose()”

The console shows setState() called after dispose(), with lifecycle state: defunct, not mounted. A screen started some work, such as an HTTP call, a Timer or a stream listener, and the user left before it finished. When the work completed, it called setState on a State object that Flutter had already removed.

Short answer

Cancel the work in dispose(): call cancel() on Timers and StreamSubscriptions, and dispose controllers. For a Future you can’t cancel, check if (!mounted) return; after the await and before setState. Flutter’s docs prefer cancelling over the mounted check, because cancelled work stops using CPU.

What called setState too late, and the fix

  1. 01

    A Future that finished after the user left

    “It is an error to call this method after the framework calls dispose.” Flutter, State.setState

    An await on an HTTP call or a database read takes a second; the user taps back; the call returns and runs setState. Check mounted right after each await that is followed by setState.

    Future<void> _load() async {
      final orders = await api.fetchOrders();
      if (!mounted) return;
      setState(() => _orders = orders);
    }
  2. 02

    A Timer or periodic Timer still running

    “it is better practice to cancel whatever work might trigger the setState rather than merely checking for mounted before calling setState” Flutter, State.setState

    Timer.periodic keeps firing after the screen is gone. Keep the Timer in a field and cancel it in dispose().

    late final Timer _timer;
    
    @override
    void initState() {
      super.initState();
      _timer = Timer.periodic(const Duration(seconds: 1), (_) => setState(() => _seconds++));
    }
    
    @override
    void dispose() {
      _timer.cancel();
      super.dispose();
    }
  3. 03

    A stream listener that was never cancelled

    “Subclasses should override this method to release any resources retained by this object” Flutter, State.dispose

    stream.listen in initState returns a StreamSubscription. Without cancel(), it calls setState on every event after the screen closes, and keeps the State in memory. Keep the subscription and cancel it in dispose(), or use a StreamBuilder, which handles it for you.

    late final StreamSubscription<User?> _sub;
    
    @override
    void initState() {
      super.initState();
      _sub = auth.authStateChanges().listen((user) => setState(() => _user = user));
    }
    
    @override
    void dispose() {
      _sub.cancel();
      super.dispose();
    }
  4. 04

    A callback passed to another object

    “It is an error to call setState unless mounted is true.” Flutter, State.mounted

    An AnimationController, an animation or controller listener, or a callback stored by a service can outlive the screen. Remove listeners in dispose() with removeListener, and dispose every AnimationController and other controller you created. Code that runs from a global service should check mounted before touching the State.

Checked against the vendor’s docs on : Flutter API: State.setState; Flutter API: State.mounted; Flutter API: State.dispose.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Flutter “Looking up a deactivated widget’s ancestor is unsafe” · Flutter “setState() called during build” · Flutter LateInitializationError · Flutter “Scaffold.of() called with a context”

Questions

More on this error

Is wrapping setState in if (mounted) enough?

It stops the error, but the timer, stream or request keeps running in the background. Cancel what you can in dispose(), and use the mounted check for one-off Futures you can’t cancel.

Does it crash the app for users?

No. The check is a debug-mode assertion, so release builds don’t show it. The work it points at still runs after the user left and can hold memory, so fix it rather than ignore it.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.