Divyang Shah
Divyang Shah

Reputation: 3997

setState() or markNeedsBuild called during build

class MyHome extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new MyHomePage2();
}

class MyHomePage2 extends State<MyHome> {
  List items = new List();

  buildlist(String s) {
    setState(() {
      print("entered buildlist" + s);
      List refresh = new List();
      if (s == 'button0') {
        refresh = [
          new Refreshments("Watermelon", 250),
          new Refreshments("Orange", 275),
          new Refreshments("Pine", 300),
          new Refreshments("Papaya", 225),
          new Refreshments("Apple", 250),
        ];
      } else if (s == 'button1') {
        refresh = [
          new Refreshments("Pina Colada", 250),
          new Refreshments("Bloody Mary", 275),
          new Refreshments("Long Island Ice tea", 300),
          new Refreshments("Screwdriver", 225),
          new Refreshments("Fusion Cocktail", 250),
        ];
      } else if (s == 'button2') {
        refresh = [
          new Refreshments("Virgin Pina Colada", 250),
          new Refreshments("Virgin Mary", 275),
          new Refreshments("Strawberry Flush", 300),
          new Refreshments("Mango Diver", 225),
          new Refreshments("Peach Delight", 250),
        ];
      } else {
        refresh = [
          new Refreshments("Absolute", 250),
          new Refreshments("Smirnoff", 275),
          new Refreshments("White Mischief", 300),
          new Refreshments("Romanov", 225),
          new Refreshments("Blender's Pride", 250),
        ];
      }

      for (var item in refresh) {
        items.add(new ItemsList(item));
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    var abc = MediaQuery.of(context).size;

    print(abc.width);

    var width = abc.width / 4;

    Text text = new Text("Dev");
    Text text2 = new Text("Sneha");
    Text text3 = new Text("Prashant");
    Text text4 = new Text("Vikesh");

    var pad = const EdgeInsets.all(10.0);

    Padding pad1 = new Padding(child: text, padding: pad);
    Padding pad2 = new Padding(child: text2, padding: pad);
    Padding pad3 = new Padding(child: text3, padding: pad);
    Padding pad4 = new Padding(child: text4, padding: pad);

    ListView listView = new ListView(children: <Widget>[
      new Image.asset('images/party.jpg'),
      pad1,
      pad2,
      pad3,
      pad4
    ]);

    Drawer drawer = new Drawer(child: listView);

    return new Scaffold(
      drawer: drawer,

      appBar: new AppBar(
        title: new Text('Booze Up'),
      ),
      body: new Column(children: <Widget>[
        new ListView.builder(
          scrollDirection: Axis.horizontal,
          itemCount: 4,
          itemBuilder: (BuildContext context, int index) {
            return new Column(children: <Widget>[
              new Container(
                child: new Flexible(
                    child: new FlatButton(
                  child: new Image.asset('images/party.jpg',
                      width: width, height: width),
                  onPressed: buildlist('button' + index.toString()),
                )),
                width: width,
                height: width,
              )
            ]);
          },
        ),
        new Expanded(
            child: new ListView(
          padding: new EdgeInsets.fromLTRB(10.0, 10.0, 0.0, 10.0),
          children: items,
          scrollDirection: Axis.vertical,
        )),
      ]),

      floatingActionButton: new FloatingActionButton(
        onPressed: null,
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class Refreshments {
  String name;
  int price;

  Refreshments(this.name, this.price);
}

class ItemsList extends StatelessWidget {
  final Refreshments refreshments;

  ItemsList(this.refreshments);

  @override
  Widget build(BuildContext context) {
    return new ListTile(
      onTap: null,
      title: new Text(refreshments.name),
    );
  }
}

Full code

I am having two errors:

1] Horizontal viewport was given unbounded height . A horizontal viewport was given an unlimited amount of vertical space in which to expand.

2] setState() or markNeedsBuild called during build A vertical renderflex overflowed by 99488 pixels.

Please help me with it . I am creating this app where on each image click a list should be shown below . The images will be in a row and the list should be shown below the row.

Thank you.

Upvotes: 341

Views: 319628

Answers (12)

rozerro
rozerro

Reputation: 7125

In my case I had to use addPostFrameCallback in ChangeNotifierProvider to solve the setState() or markNeedsBuild called during build error.

//change model state
...
WidgetsBinding.instance.addPostFrameCallback((_){
  notifyListeners();
});

Upvotes: 4

Mimu Saha Tishan
Mimu Saha Tishan

Reputation: 2623

Problem : I have faced this issue while navigate to another page using bloc state as below

if(state is LoginBtnClickState) {
    Navigator.push(context, MaterialPageRoute(builder: (_) => const Homepage()));
}

Error occurred when doing something before the page build is finished.

Solution : in such cases you should use a callback function

if(state is LoginBtnClickState) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
        Navigator.push(context, MaterialPageRoute(builder: (_) => const Homepage()));
    });
}

Upvotes: 4

Luke Hutchison
Luke Hutchison

Reputation: 9190

You don't want to just willy-nilly add a post-frame callback to call call setState, like the top answer suggests.

The reason is that if build is not currently running, then there may be no post-frame callback until the user physically interacts with the app, causing another frame to be drawn. This could mean that setState doesn't get called for an arbitrarily long period of time.

This happens when the last frame of animation has already been displayed, and build is not currently running. (For example, you could trigger this combination of conditions from a Timer.)

Additionally, if your setState could be called from anywhere (including from a Timer), then you need to check if the widget is mounted before calling setState, or you could trigger an error saying that the widget is not mounted.

The solution is to add the following State extension to your project as safe_update_state.dart, then call safeSetState where you would normally call setState:

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

extension SafeUpdateState on State {
  void safeSetState(void Function() updaterFunction) {
    void callSetState() {
      // Can only call setState if mounted
      if (mounted) {
        // ignore: invalid_use_of_protected_member
        setState(updaterFunction);
      }
    }

    if (SchedulerBinding.instance.schedulerPhase ==
        SchedulerPhase.persistentCallbacks) {
      // Currently building, can't call setState --
      // need to add post-frame callback
      SchedulerBinding.instance.addPostFrameCallback((_) => callSetState());
    } else {
      callSetState();
    }
  }
}

Upvotes: 5

Hammad Tariq
Hammad Tariq

Reputation: 13431

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 callback 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()));
});

Upvotes: 623

Ebuzer SARIYERLİOĞLU
Ebuzer SARIYERLİOĞLU

Reputation: 818

In my case problem is GetBuilder()

My Code previously.

UserDataUptade(Map<String,dynamic> newUserData){
  userData=newUserData;
  update();
}

I changed my code to:

UserDataUptade(Map<String,dynamic> newUserData){
  userData=newUserData;
  WidgetsBinding.instance.addPostFrameCallback((_) {
    update();
  });
}

and it's works

Upvotes: 0

Kashif Ahmad
Kashif Ahmad

Reputation: 263

Just remove the curly bracket {} from onTap() in case of InkWell and onPressed() in case of GestureDetector or buttons etc.

Upvotes: 3

adi
adi

Reputation: 1130

In my case, I was using GetX and a Slider. To my slider, I used a the Rx variable directly instead of the actual value.

My Code previously.

RxDouble sliderValue = 1.0.obs;

MySlider(
   value: this.sliderValue
)

I changed my code to:

MySlider(
   value: this.sliderValue.value
)

Now the error is gone.

Upvotes: 0

Hardik Chitroda
Hardik Chitroda

Reputation: 67

when you have use onWillpop method for going back then come this type of error

onWillPop: onBackPressedWillPopeback());

replace with this

onWillPop: () => onBackPressedWillPopeback());

I am sure your error will be solved... If any other query ask me any time...

Upvotes: 1

Dev Aggarwal
Dev Aggarwal

Reputation: 8516

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();

Upvotes: 32

Eric Pleines
Eric Pleines

Reputation: 221

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!

Upvotes: 13

alfiepoleon
alfiepoleon

Reputation: 2051

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();
});

Upvotes: 138

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657108

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()

Upvotes: 210

Related Questions