setState() or markNeedsBuild called during build

Solution 1:

In my case I was calling the setState method before the build method had completed the process of building the widgets.

You can face this error if you are showing a snackBar or an alertDialog before the completion of the build method, as well as in many other cases. So, in such cases you should use a call back function as shown below:

WidgetsBinding.instance.addPostFrameCallback((_){

  // Add Your Code here.

});

or you can also use SchedulerBinding which does the same:

SchedulerBinding.instance.addPostFrameCallback((_) {

  // add your code here.

  Navigator.push(
        context,
        new MaterialPageRoute(
            builder: (context) => NextPage()));
});

Solution 2:

Your code

onPressed: buildlist('button'+index.toString()),

executes buildlist() and passes the result to onPressed, but that is not the desired behavior.

It should be

onPressed: () => buildlist('button'+index.toString()),

This way a function (closure) is passed to onPressed, that when executed, calls buildlist()

Solution 3:

I was also setting the state during build, so, I deferred it to the next tick and it worked.

previously

myFunction()

New

Future.delayed(Duration.zero, () async {
  myFunction();
});

Solution 4:

The problem with WidgetsBinding.instance.addPostFrameCallback is that, it isn't an all encompassing solution.

As per the contract of addPostFrameCallback -

Schedule a callback for the end of this frame. [...] This callback is run during a frame, just after the persistent frame callbacks [...]. If a frame is in progress and post-frame callbacks haven't been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.

That last line sounds like a deal-breaker to me.

This method isn't equipped to handle the case where there is no "current frame", and the flutter engine is idle. Of course, in that case, one can invoke setState() directly, but again, that won't always work - sometimes there just is a current frame.


Thankfully, in SchedulerBinding, there also exists a solution to this little wart - SchedulerPhase

Let's build a better setState, one that doesn't complain.

(endOfFrame does basically the same thing as addPostFrameCallback, except it tries to schedule a new frame at SchedulerPhase.idle, and it uses async-await instead of callbacks)

Future<bool> rebuild() async {
  if (!mounted) return false;

  // if there's a current frame,
  if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) {
    // wait for the end of that frame.
    await SchedulerBinding.instance.endOfFrame;
    if (!mounted) return false;
  }

  setState(() {});
  return true;
}

This also makes for nicer control flows, that frankly, just work.

await someTask();

if (!await rebuild()) return;

await someOtherTask();

Solution 5:

I recieved this error due to a pretty dumb mistake, but maybe someone is here because he did the exact same thing...

In my code i have two classes. One class (B) is just there to create me a special widget with a onTap function. The function that should get triggered by the user tap was in the other class (A). So i was trying to pass the function of class A to the constructor of class B, so that i could assign it to the onTap.

Unfortunatly, instead of passing the functions i called them. This is what it looked like:

ClassB classBInstance = ClassB(...,myFunction());

Obviously, this is the correct way:

ClassB classBInstance = classB(...,myFunction);

This solved my error message. The error makes sense, because in order for myFunction to work the instance of class B and so my build had to finish first (I am showing a snackbar when calling my function).

Hope this helps someone one day!