daveoncode
daveoncode

Reputation: 19588

Flutter exception `setState() or markNeedsBuild() called during build` by calling setState in a NotificationListener callback

I'm having an hard time trying to figure out how to update a piece of a view based on a child view "event"... let me explain:

I have a screen which is composed by a Scaffold, having as a body a custom widget which calls a rest api to get the data to display (it makes use of a FutureBuilder), then it dispatch a Notification (which basically wraps the Flutter's AsynchSnapshot) that should be used in order to update the floatingActionButton (at least in my mind :P).

This is the build method of the screen:

  @override
  Widget build(BuildContext context) {
    return NotificationListener<MyNotification>(
      onNotification: onMyNotification,
      child: Scaffold(
        appBar: MyAppBar(),
        body: MyRemoteObjectView(),
        floatingActionButton: MyFloatingButton(),
      ),
    );
  }

The view is rendered perfectly, the data is retrieved from the server and displayed by MyRemoteObjectView and the notification is successfully dispatched and received, BUT as soon as I call setState() in my callback, I get the exception:

setState() or markNeedsBuild() called during build.

This is the callback (defined in the same class of the build method above):

bool onMyNotification(MyNotification notification) {
    AsyncSnapshot snapshot = notification.snapshot;

    if (snapshot.connectionState == ConnectionState.done) {
      setState(() {
        // these flags are used to customize the appearance and behavior of the floating button
        _serverHasBeenCalled = true;
        _modelDataRetrieved = snapshot.hasData;
      });
    }

    return true;
}

This is the point in which I send the notification (build method of MyRemoteObjectView's state):

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<T>(
      future: getData(),
      builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
        MyNotification(snapshot).dispatch(context);
        // ...

The point is: how and when should I tell Flutter to redraw the floating button (and/or other widgets)? (because of course without the setState I don't have the exception but the button is not refreshed)

Am I getting the whole thing wrong? Is there an easier way to do it? Let me know

Upvotes: 0

Views: 3124

Answers (1)

Igor Kharakhordin
Igor Kharakhordin

Reputation: 9903

After FutureBuilder is built, it waits for future to return a value. After it is complete, you're calling setState and then FutureBuilder would be built again and so on, resulting in infinite repaint loop.

Are you sure that you need FutureBuilder and NotificationListener in this case? You should probably do it in initState of your StatefulWidget like this:

@override
void initState() {
  super.initState();

  getData().then((data) {
    setState(() {
      _serverHasBeenCalled = true;
      _modelDataRetrieved = true;
    });
  });
}

You can also store Future in a state and pass it to FutureBuilder.

Upvotes: 2

Related Questions