182

When I click the raised button, the timepicker is showing up. Now, if I wait 5 seconds, for example, and then confirm the time, this error will occur: setState() called after dispose()

I literally see in the console how flutter is updating the parent widgets, but why? I don't do anything - I just wait 5 seconds?! The example below will work in a normal project, however in my project which is quite more complex it won't work because Flutter is updating the states while I am waiting... What am I doing wrong? Does anyone have a guess at what it could be that Flutter is updating randomly in my more complex project and not in a simple project?

[UPDATE] I took a second look at it and found out it is updating from the level on where my TabBar and TabBarView are. Could it have to do something with the "with TickerProviderStateMixin" which I need for the TabBarView? Could it be that it causes the app to refresh regularly and randomly?

 class DateTimeButton extends State<DateTimeButtonWidget> {
  DateTime selectedDate = new DateTime.now();

  Future initTimePicker() async {
    final TimeOfDay picked = await showTimePicker(
      context: context,
      initialTime: new TimeOfDay(hour: selectedDate.hour, minute: selectedDate.minute),
    );

    if (picked != null) {
      setState(() {
        selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
      onPressed: () {
        initTimePicker();
      }
    );
  }
}
Dre
  • 39
  • 3
GreenTigerEye
  • 5,917
  • 9
  • 22
  • 33

9 Answers9

418

Just check boolean property mounted of the state class of your widget before calling setState().

if (this.mounted) {
  setState(() {
    // Your state change code goes here
  });
}

Or even more clean approach Override setState method in your StatelfulWidget class.

class DateTimeButton extends StatefulWidget {
  @override
  void setState(fn) {
    if(mounted) {
      super.setState(fn);
    }
  }
}
Falko
  • 17,076
  • 13
  • 60
  • 105
Ganapat
  • 6,984
  • 3
  • 24
  • 38
  • 22
    it just prevents setting state, but doesn't solve the problem of not being able to set state – temirbek Apr 03 '19 at 00:16
  • 1
    @temirbek I am not sure what your use-case is, the purpose of that if-condition is exactly to stop trying to set the state once the widget is not mounted, means widget does not exist in the hierarchy of widgets on the screen. – Ganapat Apr 06 '19 at 18:52
  • 1
    ok, my use case is Screen A calls Screen B and waits for a result. Screen B returns result, but by that time Screen A recreates, and old one that was waiting for result is unmounted. But I need to setState in ScreenA with the value that was returned from ScreenB – temirbek Apr 07 '19 at 08:45
  • 1
    In your use case, you either have to make sure ScreenA has the same instance when ScreenB returns, check if ScreenA key is same or not, if that can not be done, and ScreenA must recreate new instance, then you will have to implement state management using some way, like ScopedModel, Bloc, or FlutterRedux etc. – Ganapat Apr 07 '19 at 09:13
  • 1
    In my case I had to override `setState` of a `ClassName extends State`. – Nae Jul 02 '19 at 17:45
  • 10
    why the framework doesn't do that check internally by default? is there any case where a state can be set for a disposed widget?! – y.allam Apr 28 '20 at 19:49
  • Not sure why! Maybe it's that way so that developers are more conscious of the way they update the widget state, like suppose we are listening to a stream and updating widget state, and widget is no longer in the hierarchy, then if we have not closed the stream subscription, we will know of this memory leak becuase of this exception being thrown! – Ganapat Apr 29 '20 at 06:07
  • This check may not work if the widget is destroying and not unmounted yet. – Alexander Pravdin Aug 10 '20 at 09:26
  • @AlexanderPravdin then what to use in such case? – user3808307 Feb 01 '21 at 14:36
  • 1
    @user3808307 I had to rebuild my flutter logic at all to avoid such cases. I would suggest trying to follow flutter guidelines on how to organize the application logic for different scenarios. – Alexander Pravdin Feb 10 '21 at 04:21
  • I am not using `setState` function on my entire screen code, just getting and updating values by using `Provider`. but still facing this issue when system tries to update provider value and at the same time user navigate to another screen, then this issue is raised. adding `if(mounted){}` condition is not professional sign of a good coder. Any suggestion will be welcomed. Thanks. – Kamlesh Jun 28 '21 at 15:20
  • 1
    This answer deserves more than 1000 votes. – Gedeon Mutshipayi Jul 15 '22 at 08:56
50

If it is an expected behavior that the Future completes when the widget already got disposed you can use

if (mounted) {
  setState(() {
    selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
  });
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 5
    I would recommend https://docs.flutter.io/flutter/widgets/State/mounted.html instead of using your own member. – Jonah Williams Mar 18 '18 at 17:41
  • 1
    Thanks mate. This saved my day (absolutely the week) :D – Kavinda Jayakody Jan 09 '20 at 13:17
  • @Gunter, how can we call setState before super.dispose(); in dispose() function. it does not work for me and getting a error message like 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4263 pos 12: '_debugLifecycleState != _ElementLifecycle.defunct': is not true. I am already using if (mounted) { setState(() {}); } Please suggest what is the issue. Thanks a lot. – Kamlesh Nov 06 '20 at 06:53
26

Just write one line before setState()

 if (!mounted) return;

and then

setState(() {
      //Your code
    });
Rahul Mahadik
  • 11,668
  • 6
  • 41
  • 54
15

I had the same problem and i solved changing the super constructor call order on initState():

Wrong code:

@override
  void initState() {
    foo_bar(); // call setState();
    super.initState(); // then foo_bar()
  }

Right code:

@override
  void initState() {
    super.initState();
    foo_bar(); // first call super constructor then foo_bar that contains setState() call
  }
teteArg
  • 3,684
  • 2
  • 20
  • 18
10

To prevent the error from occurring, one can make use of the mounted property of the State class to ensure that a widget is mounted before settings its state:

// First Update data 

if (!mounted) { 
      return;
 }
setState(() { }
Dre
  • 39
  • 3
DimarBorda
  • 101
  • 1
  • 3
  • 7
    When writing an answer please write a few sentence along with your code and do not drop it with only one word. – MisterNox Jul 15 '20 at 21:26
5
class MountedState<T extends StatefulWidget> extends State<T> {
  @override
  Widget build(BuildContext context) {
    return null;
  }

  @override
  void setState(VoidCallback fn) {
    if (mounted) {
      super.setState(fn);
    }
  }
}

Example

To prevent the error,Instead of using State use MountedState

class ExampleStatefulWidget extends StatefulWidget {
  const ExampleStatefulWidget({Key key}) : super(key: key);

  @override
  _ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}

class _ExampleStatefulWidgetState extends MountedState<ExampleStatefulWidget> {
  ....
}
Karzan Kamal
  • 1,074
  • 10
  • 9
3

The problem could occur when you have long asynchronous operation in stateful widget that could be closed/disposed before the operation finished.

Futures in Dart are not preemptive, so the only way is to check if a widget mounted before calling setState.

If you have a lot of widgets with asynchrony, adding ton of if (mounted) checks is tedious and an extension method might be useful

extension FlutterStateExt<T extends StatefulWidget> on State<T> {
  void setStateIfMounted(VoidCallback fn) {
    if (mounted) {
      // ignore: invalid_use_of_protected_member
      setState(fn);
    }
  }
}
Pavel Shastov
  • 2,657
  • 1
  • 18
  • 26
2

Try this

Widget build(BuildContext context) {
    return new RaisedButton(
        child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
        onPressed: () async {
            await initTimePicker();
        }
    );
}
Dale K
  • 25,246
  • 15
  • 42
  • 71
PrimeNexes
  • 565
  • 1
  • 7
  • 17
0

I had this error when I mistakenly called super.initState before the variable. Check this:

@override
void initState() {
super.initState();
   bloc = MainBloc();
  }

Should be fixed as

@override
void initState() {
   bloc = MainBloc();
super.initState(); 
  }
Kipruto
  • 721
  • 6
  • 16