setState() or markNeedsBuild called during build

后端 未结 6 1650
自闭症患者
自闭症患者 2020-12-04 16:14
class MyHome extends StatefulWidget {
  @override
  State createState() => new MyHomePage2();
}

class MyHomePage2 extends State

        
相关标签:
6条回答
  • 2020-12-04 16:57

    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();
    });
    
    0 讨论(0)
  • 2020-12-04 17:02

    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();
    
    0 讨论(0)
  • 2020-12-04 17:04

    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!

    0 讨论(0)
  • 2020-12-04 17:05

    I have recreated what you are trying to do, with only two of your Refreshment items, you probably want to refactor things and handle the layout in a better way, but just for the sake of simplicity try to follow this example and compare it to what you are trying to achieve:

    import "package:flutter/material.dart";
    
    class LetsParty extends StatefulWidget {
      @override
      _LetsPartyState createState() => new _LetsPartyState();
    }
    
    class _LetsPartyState extends State<LetsParty> {
      Image _partyImage = new Image.network(
          "http://www.freshcardsgifts.co.uk/images/_lib/animal-party-greetings-card-3003237-0-1344698261000.jpg");
    
      final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
    
      List<List> list = [
        ["Pina Colada",
        "Bloody Mary",
        "Strawberry Flush",
        "Mango Diver",
        "Peach Delight"
        ],
        [
          "Absolute",
          "Smirnoff",
          "White Mischief",
          "Romanov",
          "Blender's Pride"
        ]
      ];
    
      List<String> visibleList = null;
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          key: scaffoldKey,
          appBar: new AppBar(title: new Text("Let's Party"),),
          body: new ListView(
            // shrinkWrap: true,
            children: <Widget>[
    
    
              new Container (
                  height: 150.0,
                  child: new ListView.builder(
                      shrinkWrap: true,
                      scrollDirection: Axis.horizontal,
                      itemCount: 2, //add the length of your list here
                      itemBuilder: (BuildContext context, int index) {
                        return new Column(
                          children: <Widget>[
    
                            ///Flexible let's your widget flex in  the given space, dealing with the overflow you have
                            //  new Flexible(child: new FlatButton(onPressed: ()=>print("You pressed Image No.$index"), child: _partyImage)),
                            new Flexible (child: new Container(
                                child: new FlatButton(onPressed: () {
                                  scaffoldKey.currentState.showSnackBar(
                                      new SnackBar(
                                          content: new Text(
                                              "You pressed Image No.$index")));
    
                                  setState(() {
                                    visibleList = list[index];
                                  });
                                },
                                    child: _partyImage),
                                width: 100.0,
                                height: 100.0
                            ),),
                            //Exact width and height, consider adding Flexible as a parent to the Container
                            new Text("Text$index"),
                          ],
                        );
                      })),
    
              new Column(children: visibleList==null?new List():new List.generate(5, (int i) {
                new ListTile(title: new Text(visibleList[i]),
                  onTap: () =>
                      scaffoldKey.currentState.showSnackBar(new SnackBar(
                        content: new Text("Your choise is ${visibleList[i]}"),)),
                );
              }))
            ],),);
      }
    }
    
    0 讨论(0)
  • 2020-12-04 17:06

    In my case I was calling setState method before build method complete process of building widgets.

    You can face this error if you are showing snack bar or alert dialog before the completion of build method and in many other cases. so in such situation use below call back function.

    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()));
    });
    
    0 讨论(0)
  • 2020-12-04 17:20

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

    0 讨论(0)
提交回复
热议问题