Asmoun
Asmoun

Reputation: 1747

how to call setState inside FutureBuilder in Flutter

I'm trying to set State based on result received by FutureBuilder, looks like it is not possible since FutureBuild is still building, any ideas please ?
error :

The following assertion was thrown building FutureBuilder<String>(dirty, state: _FutureBuilderState<String>#db9f1):
setState() or markNeedsBuild() called during build.

This StatefulBuilder 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.
The widget on which setState() or markNeedsBuild() was called was: StatefulBuilder

my code :

FutureBuilder<String>(
                                future: fetchIdPlayer(idPlayer, bouquet),
                                builder: (context, snapid) {
                                  if (!snapid.hasData)
                                    return Container(
                                      height: mobileHeight * 0.05,
                                      child: Center(
                                        child: CircularProgressIndicator(),
                                      ),
                                    );
                                  else if (snapid.data == "Error_ID") {
                                    setState(() {
                                      have_ID = true;
                                      resultName = "رقم تعريف اللاعب خاطئ";
                                    });
                                  }
                                })

Upvotes: 2

Views: 2361

Answers (5)

rosh-dev851
rosh-dev851

Reputation: 564

Flutter prohibit this kind of situation because setting a state in side a future widget will cause to rebuild the future widget again and this will become an infinite loop. Try to separate logic from UI. Example credits goes to @pskink and @Mofidul Islam

In your state class create a wrapper

Future<String> wrapper(idPlayer, bouquet) async { 
final foo = await fetchIdPlayer(idPlayer, bouquet); 
//handle errors
if(foo.error){
   have_ID = true;
   resultName = "رقم تعريف اللاعب خاطئ";
}
return foo; 
}

call the wrapper in your widget

FutureBuilder<String>(
   future: wrapper(idPlayer, bouquet),
   builder: (context, snapid) {
    if (!snapid.hasData)
       return Container(
           height: mobileHeight * 0.05,
           child: Center(
              child: CircularProgressIndicator(),
                ),
             );
    else {
     //UI to show error                       
         }
    })

Upvotes: 2

jwehrle
jwehrle

Reputation: 4784

The key bit of this error message is, "called during build". You need to wait until after build. This can be accomplished with either:

WidgetsBinding.instance?.addPostFrameCallback()

https://api.flutter.dev/flutter/widgets/WidgetsBinding-mixin.html

or

SchedulerBinding.instance?.addPostFrameCallback()

https://api.flutter.dev/flutter/scheduler/SchedulerBinding-mixin.html

Upvotes: 3

croxx5f
croxx5f

Reputation: 5718

You can workaround the error you are getting by scheduling the setState to be executed in the next frame and not potentially during build.

  else if (snapid.data == "Error_ID") {    
    WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
///This schedules the callback to be executed in the next frame
/// thus avoiding calling setState during build

      setState(() {
     have_ID = true;
     resultName = "رقم تعريف اللاعب خاطئ";
      });
    });
...

Upvotes: 4

jeremynac
jeremynac

Reputation: 1242

You could use the property connectionState of snapid. This should generally work as connectionState is set to ConnectionState.done whenever the future is terminated.

FutureBuilder<String>(
                                future: fetchIdPlayer(idPlayer, bouquet),
                                builder: (context, snapid) {
                                  if (snapshot.connectionState == ConnectionState.done) {
                                      setState((){
                                          //...
                                      })
                                  }
                                  if (!snapid.hasData)
                                      //...
                                  else if (snapid.data == "Error_ID") {
                                      //...
                                  }
                                })

Upvotes: 3

Mofidul Islam
Mofidul Islam

Reputation: 520

You can just wrap the widget who will use resultName and have_ID with FutureBuilder .So there is no need to setState.you can also handle error as well .If you want to setState then use a asyn function and after result is fetched you can just call setState

Upvotes: 3

Related Questions