skip to Main Content

I have a widget showing a list of elements that are loaded asynchronously. So in the initState method I call onto the async loading method, which then calls onto the parent Scaffold to refresh itself (to show the element count in the AppBar).
This works great, however I run into an issue if the list of elements is loaded synchronously :

setState() or markNeedsBuild() called during build.
I/flutter ( 6150): This ElementsList widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.

This seems logical, as I am asking a widget that is currently being built (the parent Scaffold) to rebuild itself (which does not happen when the elements list is loaded in async).

My question is : how can I know if Flutter is "in the process of building widgets" ? I could of course keep track of this in a data-driven way (if my data source is synchronous, do not rebuilt the Scaffold on the first build), but I’d rather this be an abstract way, in order to re-use it in other widgets if I ever need to.


Note : This only happens when the parent Scaffold is built for the first time. Here is a short recap of what is happening for clarity :

Async : Scaffold.build() => ElementsList.initState() => ElementsList.loadData() => async gap => Scaffold.setState()

Sync : Scaffold.build() => ElementsList.initState() => ElementsList.loadData() => Scaffold.setState() => Raising the error because setState was called during build.


I have a seen a lot of Stackoverflow answers about this, but they always work by adding an async gap before the call to setState. While this works, this treats the symptom instead of the illness and is not what I’m looking to learn here.

2

Answers


  1. Chosen as BEST ANSWER

    It seems the doc comment of SchedulerBinding.ensureVisualUpdate() considers the transientCallbacks, midFrameMicrotasks and persistentCallbacks states of the enum SchedulerPhase as significating that a frame is being build or rendered, so I'll consider it the same. This is info can be accessed by looking at WidgetsBinding.instance.schedulerPhase.

    Here's some code to easily access this info :

      static bool get isBuilding => [
        SchedulerPhase.transientCallbacks,
        SchedulerPhase.midFrameMicrotasks,
        SchedulerPhase.persistentCallbacks,
      ].contains(WidgetsBinding.instance.schedulerPhase)
          || WidgetsBinding.instance.renderViewElement == null;
    
    
      /// setState(() {}). If Flutter is currently building, called post-frame.
      void refresh(void Function() fn) {
        if(mounted) {
          if(isBuilding) {
            SchedulerBinding.instance.addPostFrameCallback((_) {
              if(mounted) {
                setState(fn);
              }
            });
          } else {
            setState(fn);
          }
        }
      }
    

    Edit : During the build of the very first frame, schedulerPhase is at idle, so I changed the code to additionally look at if a view exists (the root of the tree).


  2. You can try to use

    WidgetsBinding.instance.addPostFrameCallback((_) => _yourFunction());
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search