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.
FutureBuilder
should be used with aStatefulWidget
in which yourFuture
is a property initialized ininitState
;- 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