ghjfgj,.mbn
Back to blog
Tutorials
5 min read

Fix: setState() called after dispose() in Flutter

Why you get 'setState() called after dispose()' and three proven ways to fix it — with code examples for async operations, streams, and timers.

Fix: setState() called after dispose()

setState() called after dispose(): _MyWidgetState#12345

This error means you're calling setState() on a widget that's already been removed from the widget tree. It's a lifecycle timing issue, and it's very common with async operations.

Why It Happens

When a widget is disposed (user navigated away, parent rebuilt without it), the State object is marked as unmounted. But if you have a pending Future, Stream, or Timer, its callback will still fire — and call setState() on a dead widget.

Fix 1: Check mounted Before setState

The simplest fix:

Future<void> _loadData() async {
  final result = await api.fetchData();
  if (!mounted) return;  // Widget was disposed during the await
  setState(() {
    _data = result;
  });
}

Fix 2: Cancel Subscriptions in dispose()

For streams and timers:

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? _subscription;
  Timer? _timer;
 
  @override
  void initState() {
    super.initState();
    _subscription = stream.listen((data) {
      setState(() => _items = data);
    });
    _timer = Timer.periodic(Duration(seconds: 5), (_) {
      setState(() => _counter++);
    });
  }
 
  @override
  void dispose() {
    _subscription?.cancel();
    _timer?.cancel();
    super.dispose();
  }
}

Fix 3: Use a Cancelable Completer

For complex async chains:

class _MyWidgetState extends State<MyWidget> {
  CancelableOperation<Data>? _operation;
 
  void _fetchData() {
    _operation?.cancel();
    _operation = CancelableOperation.fromFuture(
      api.fetchData(),
    );
    _operation!.value.then((data) {
      if (mounted) setState(() => _data = data);
    });
  }
 
  @override
  void dispose() {
    _operation?.cancel();
    super.dispose();
  }
}

InkPal Auto-Fix

InkPal detects this pattern and adds the mounted guard automatically:

inkpal.self_heal()
// Detects: setState after dispose pattern
// Fix: Adds mounted check before every setState in async context

Prevention Rules

  1. Every await in a State method needs a mounted check after it
  2. Every StreamSubscription needs cancellation in dispose()
  3. Every Timer needs cancellation in dispose()
  4. Consider using StatefulWidget alternatives (Riverpod, BLoC) that handle this automatically

Look up any Flutter error at /errors — free, instant, no signup required.