Change state in FutureBuilder when the data is loaded - flutter

How can I update the UI if I need to wait for the FutureBuilder? Do I need to call my future function twice, one for for the builder and one again to change the UI?
FutureBuilder<String>(
future: getUserOrder(4045),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data,style: Theme.of(context).textTheme.headline);
} else if (snapshot.hasError) {
// I need to change the state at this point
return Text("${snapshot.error}",style: Theme.of(context).textTheme.headline);
} else {
return CircularProgressIndicator();
}
}),
Calling setState inside the FutureBuilder throws this error:
setState() or markNeedsBuild() called during build.
I don't need to display a button or any other other to be clicked. I want to perform the action automatically when the date is loaded in the futureBuilder

Since I couldn't call setState inside FutureBuilder the solution was remove it and do something like this:
getBillingInfo() {
Provider.of<MyRents>(context, listen: false)
.getBillingInfo(context)
.then((billingInfo) {
setState(() {
if (billingInfo["companyInfo"] != null &&
billingInfo["taxes"].isNotEmpty) {
_canGenerateInvoices = true;
} else {
_canGenerateInvoices = false;
}
});
});
}
...
void initState() {
super.initState();
getBillingInfo();
}
...
Visibility(
visible: _canGenerateInvoices,
child: MyWidget()
)
Having this, when I perform other actions I can always change the value of _canGenerateInvoices

Related

setstate futurebuilder flutter

Solved!
I am getting date from FireBase via a futurebuilder.
It returns a Row whose children is a list of a widget i created called SmallDogCard (since my app is about dogs).
On a screen i want users to be able to press on the SmallDogCard to select it and change the border color, howerver. This causes the futurebuilder to load the data again... How should i approach this?
My code:
Futurebuilder:
FutureBuilder(
future: DogOwnerModel().getUserData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('error');
}
if (snapshot.connectionState ==
ConnectionState.done) {
List<SmallDogCard> dogCardList = [];
final userData =
snapshot.data as Map<String, dynamic>;
List<Map<String, dynamic>>? userDogs =
userData['user dog data'];
if (userDogs == null) {
return Text('no dogs yet');
} else {
for (Map<String, dynamic> dog
in userDogs) {
dogCardList.add(SmallDogCard(
dogName: dog['name'],
imageUrl: dog['profile image'],
isSelected: selectedDogs
.contains(dog['name'])
? true
: false,
selectCallback: selectDogCallback));
}
return Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: dogCardList);
}
}
return Text('Loading');
},
),
Onpress function callback passed to the SmallDogCard (which uses setstate and makes the futurbuilder get the data again).
selectDogCallback(String name, bool isSelected) {
if (isSelected == false) {
selectedDogs.add(name);
} else {
selectedDogs.remove(name);
}
setState(() {
selectedDogs;
});
}
How can i accomplish this without the futurebuilder being called again?
So i can setstate which changes the border color of the SmallDogCard which is returned from the FutureBuilder.
Thank you in advance!
Problem solved!
Initializing the future in initstate, so after initstate it wont be called again!
Like this:
#override
void initState() {
// TODO: implement initState
super.initState();
_future = setFuture();
}
setFuture() async {
return await DogOwnerModel().getUserData(email: email);
}

calling setState in flutter Bloc listener

I am calling setState in flutter BlocListener. is there any problem doing this?
....
return BlocListener<XBloc, XState>(
listener: (context, state) {
if (state is XLoadedState) {
setState(() {
name = state.name;
});
}....
....
It's not a problem but it's kinda useless and anti pattern. And using setState you are forcing everything to rebuild even if it's not necessary.
You could just wrap the widget that uses name into a BlocBuilder<XBloc,XState>, for example like this:
BlocBuilder<XBloc,XState>(
builder: (context, state){
if (state is XLoadedState){
return Text(state.name);
}else{
//return something for when state.name is null, I guess
}
}
)
You can check more about this here

Fixing Issues with FutureBuilder

In my Flutter project, I am trying to implement a button click event by using FutureBuilder. Basically when the button clicked, it supposed to get the data and display in a table. So my button onPressed event handling is as below:
onPressed: () async{
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var p = double.parse(loanAmount);
var r = double.parse(interestRate);
var n = int.parse(monthes);
Api api = new Api();
new FutureBuilder<List>(
future: api.calculateEmi(p, r, n),
builder: (BuildContext buildContext, AsyncSnapshot<List> snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
print( snapshot.data);
return new SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: DataTableWidget(listOfColumns: snapshot.data.map(
(e)=>{
'Month': e['Month'],
'Principal': e['Principal'],
'InterestP': e['InterestP'],
'PrinciplaP': e['PrinciplaP'],
'RemainP': e['RemainP']
}).toList()
),
);
}
}
);
}
}
The Api call is working and the method calculateEmi is called and get data returned ( a List of Map), but the view just not updated and no table appeared at all, and I use breakpoint at the builder portion but it never go into it, where did I do wrong, can anyone help? thanks.
The FutureBuilder needs to be inserted somewhere in the flutter widget tree. Simply creating a new FutureBuilder doesn't tell flutter what to do with it.
I'm guessing you instead want to put the FutureBuilder you created somewhere in the parent widget of the onPressed method. If you need it to only show when the button is pressed you can do that with a bool that determines whether to show the FutureBuilder or not.
Ex.
Widget build(context) {
if(buttonPressed) {
return FutureBuilder(
...
);
}
else {
return Container();
}
}

How to listen for a bunch of Futures without a FutureBuilder?

I have built a FutureBuilder to check Futures then redirect based on that, but as it's a FutureBuilder, I have to return the Screen NOT ROUTING to them.
Can somebody share an example of how to listen for a bunch of futures without a FutureBuilder, so I can Route with animation instead of dummy return.
FutureBuilder(
future: Future.wait([
firstFuture(),
secondFuture(),
]),
builder: (
context,
AsyncSnapshot<List<bool>> snapshot,
){
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
// If all future passed return home.
if (snapshot.data[0] && snapshot.data[1])
// Instead I want: MaterialPageRoute(builder: (_) => HomeScreen());
return HomeScreen();
// Instead I want: MaterialPageRoute(builder: (_) => permissionsScreen());
return permissionsScreen();
}
);
Based on pskink's advice, I implemented this code which will loop and wait for a bunch of Futures to finish, then route or whatever without a FutureBuilder:
//.. inside a StatfulWidget or whatever...
// Loop thru Dependencies List(Futures) and make sure all are loaded(finished).
Future<bool> loadDependencies(List<Future<bool>> dependenciesList) async {
bool isAllDone = true;
// Load dependencies.
for (Future<bool> dependency in dependenciesList) {
bool currentDep = await dependency;
isAllDone = isAllDone && currentDep;
if (!isAllDone) return false; // Break if any failed.
}
return isAllDone;
}
Then call it, here's an example:
//.. inside a StatfulWidget
// Define App's Dependencies List.
final List<Future<bool>> dependenciesList = [
someBloc.fetchData(),
];
#override
void initState() {
super.initState();
// Load dependencies then route once done.
loadDependenciesAndRoute();
}
void loadDependenciesAndRoute() async {
bool loadingStatus = await loadDependencies(dependenciesList);
if (loadingStatus) {
// As Context is not ready inside initState, routing using navigatorKey.
await AppKeys.navigatorKey.currentState.push(
Routes.home(),
);
} else {
await AppKeys.navigatorKey.currentState.push(
Routes.onLoading(),
);
}
}

How to inform FutureBuilder that database was updated?

I have a group profile page, where a user can change the description of a group. He clicks on the description, gets on a new screen and saves it to Firestore. He then get's back via Navigator.pop(context) to the group profile page which lists all elements via FutureBuilder.
First, I had the database request for my FutureBuilder inside the main build method (directly inside future builder 'future: request') which was working but I learnt it is wrong. But now I have to wait for a rebuild to see changes. How do I tell FutureBuilder that there is a data update?
I am loading Firestore data as follows within the group profile page:
Future<DocumentSnapshot> _future;
#override
void initState() {
super.initState();
_getFiretoreData();
}
Future<void> _getFiretoreData() async{
setState(() {
this._future = Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();});
}
The FutureBuilder is inside the main build method and gets the 'already loaded' future like this:
FutureBuilder(future: _future, ...)
Now I would like to tell him: a change happened to _future, please rebuild ;-).
Ok, I managed it like this (which took me only a few lines of code). Leave the code as it is and get a true callback from the navigator to know that there was a change on the second page:
// check if second page callback is true
bool _changed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfileUpdate(userId: globals.userId.toString())),
);
// if it's true, reload future data
_changed ? _getFiretoreData() : Container();
On the second page give the save button a Navigator.pop(context, true).
i would advice you not to use future builder in this situation and use future.then() in an async function and after you get your data update the build without using future builder..!
Future getData() async {
//here you can call the function and handle the output(return value) as result
getFiretoreData().then((result) {
// print(result);
setState(() {
//handle your result here.
//update build here.
});
});
}
How about this?
#override
Widget build(BuildContext context) {
if (_future == null) {
// show loading indicator while waiting for data
return Center(child: CircularProgressIndicator());
} else {
return YourWidget();
}
}
You do not need to set any state. You just need to return your collection of users in your GetFirestoreData method.
Future<TypeYouReturning> _getFirestoreData() async{
return Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();
}
Inside your FutureBuilder widget you can set it up something like Theo recommended, I would do something like this
return FutureBuilder(
future: _getFirestoreData(),
builder: (context, AsyncSnapshot<TypeYouReturning> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Text("No available data just yet");
return Container();//This should be the desire widget you want the user to see
}
},
);
Why don't you use Stream builder instead of Future builder?
StreamBuilder(stream: _future, ...)
You can change the variable name to _stream for clarity.