Home > Mobile >  Rebuild only FutureBuilder widget
Rebuild only FutureBuilder widget

Time:08-22

I am getting a random value from some API, and I have a condition for it. If the condition is true, I will return a widget, else I want to change the random value and get another random value from the API again. I need to rebuild just the FutureBuilder widget, and I dont know how to do this, I got an error while using setState which is setState() or markNeedsBuild() called during build. This is my code so far:

FutureBuilder<List<dynamic>>(
                    future: API.get_pets(randomly_select_URL()),
                    builder: (context, snapshot) {
                      if (snapshot.hasData) {
                        List<dynamic>? pet_data = snapshot.data;

                        if (dropDownIsSelected == true) {
                          var number_of_parameters = snapshot.data!.length;
                          var random_pet = random.nextInt(number_of_parameters);
                          var category =
                              pet_data![random_pet].category.toString();
                          var photoURL =
                              pet_data![random_pet].photoUrls.toString();
                          if (notEqualsIgnoreCase(category, "Kitty") ||
                              notEqualsIgnoreCase(category, "Puppy") ||
                              notEqualsIgnoreCase(category, "Fish") ||
                              notEqualsIgnoreCase(category, "Hedgehog") ||
                              notEqualsIgnoreCase(category, "Bunny") ||
                              photoURL.length == 0) {
                                print("NOT IN CATEGORY");
                                random_pet = random.nextInt(number_of_parameters);
                              }
                          else {
                            return Random_Card();
                          }
                          
                        }

CodePudding user response:

I'm not sure this is what you need, but I see two potential errors.

  1. FutureBuilder should be used with a StatefulWidget in which your Future is a property initialized in initState;
  2. The error you're receiving is telling you: "you asked me to perform a rebuild even though I didn't even finish to render this frame, and that's awkard". By design, the Flutter framework throws in these scenarios.

It's easier done than said, so, here's what I'd try (I didn't test this):

// Warning: pseudocode ahead!

class MyWidget extends StatefulWidget {
  MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Future<int>? myFuture;

  @override
  void initState() {
    super.initState();
    myFuture = // your API call
  }

  void requestAgain() {
    setState(() {
      myFuture = // another API call
    });
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: myFuture,
      builder: (context, snapshot) {
        var output = "";
        if (snapshot.connectionState == ConnectionState.waiting) output = "loading...";
        if (snapshot.hasData) {
          output = "Now we have: ${snapshot.data}";
          if (someCondition) {
            // !
            WidgetsBinding.instance.addPostFrameCallback((_){
              requestAgain();
            });
          }
        }
        if (snapshot.hasError) output = "Woops, something went wrong";

      },
    );
  }
}

As you can see when someCondition is true we're appending your API call after Flutter finished rendering the current frame; this is a hacky workaround though, that might or might not be the ideal solution.

I highly recommend to use Riverpod to handle this problem (see FutureProvider).

CodePudding user response:

In a Statefull or Stateless widget, mix a StatefullBuilder and a FutureBuilder to reset only the StatefullBuilder content.

Here is a working example with a StatelessWidget:

class MyWidget extends StatelessWidget {
  Future myFuture() async {
    print("reloading");
    await Future.delayed(Duration(seconds: 1));
    return true;
  }

  @override
  Widget build(BuildContext context) {
    return StatefulBuilder(builder: (context, subState) {
      return Column(children: [
        FutureBuilder(
            future: myFuture(),
            builder: (context, snapShot) {
              return Text('hasData:${snapShot.hasData} | at: ${DateTime.now()}');
            }),
        InkWell(
            onTap: () {
              subState(() {});
            },
            child: Text("Call Future"))
      ]);
    });
  }
}

CodePudding user response:

just use Future.delayed(Duration.zero) in the top of your future function and all things will works fine

Future<void> testFunction(){
Future.delayed(Duration.zero);
//then type your future code here
}

or if you are using alert dialog or anything else you have to use delayed duration in the top of your code

  • Related