This is My Code.
Future<void> SendOrderDetails() async{
Row(
children: [
FutureBuilder(
future: topcart.getData(),
builder: (context, AsyncSnapshot<List<Cart>> snapshot) {
for(int i = 0; i<itemcount; i++)
{
if(itemcount>listModel2.data!.length) {
listModel2.data?.add(Model2(
ORDER_INFO_ID: 1,
PRODUCT_ED_ID: 2,
QTY: quantitycalcule.toString(),
UNIT_PRICE:'00',// snapshot.data![i].Book_initional_price!.toString(),
CHGED_BY:1,
CHGED_DATE: DateTime.now().toString(),
STATUS: 'P',
),);
}
}
return const Text('');
}
),
],
);
}
When I Call This, "FutureBuilder" did not run. I need "snapshot" in If condition. Please Help me.
I'm not sure what your code is trying to accomplish but there are a few things I can see that could be potentially causing issues:
You are calling FutureBuilder inside a Future and there is no await inside your future so it's really not going to work right.
The whole point of a FutureBuilder is to builds itself based on the latest snapshot of interaction with a Future. Maybe you can explain why this structure is the way it is.
topcart.getData() - Should not be in the future builder. You need to get something like
// This needs to be outside the build method, maybe initState()
Future<TYPE> _gd = topcart.get()
// Future Builder in the build method
FutureBuilder<TYPE>(
future: _gd,
builder: (BuildContext context, AsyncSnapshot<TYPE> snapshot) {});
Ideally, within the FutureBuilder() you want to check connection and data like:
if (snapshot.connectionState == ConnectionState.waiting) {
return // Progress indicator widget
} else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return // Error Widget or Text
} else if (snapshot.hasData) {
return // Process data here
} else {
return // Empty set returned
}
} else {
return Text('State: ${snapshot.connectionState}');
}
this method returns nothing so you don't have way to check whether the future builder run or not, try to use print and check your debug console (or try to debug your code by using break points) if it works then do what ever you want to do
additional question: what state management you are using?
The whole point of FutureBuilder is to build (and so to return) a Widget based on a snapshot.
As it seems you don't need this Widget at all in your code, couldn't you just skip the Builder altogether ?
Something like this :
Future<void> SendOrderDetails() async {
var data = await topcart.getData();
for (int i = 0; i < itemcount; i++) {
if (itemcount > listModel2.data!.length) {
listModel2.data?.add(
Model2(
ORDER_INFO_ID: 1,
PRODUCT_ED_ID: 2,
QTY: quantitycalcule.toString(),
UNIT_PRICE: data![i].Book_initional_price!.toString(),
CHGED_BY: 1,
CHGED_DATE: DateTime.now().toString(),
STATUS: 'P',
),
);
}
}
}
Related
I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??
This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.
I have an asynchronous function that obtains information from my bd in firebase, when debugging this code fragment I can see that the data is obtained without any problem, this data will be used to display in widgets and I pass it through a future builder, the problem is that although when debugging I realize that the data are there, Future builder does not detect them and snapshot has null value, it is until after several iterations when snapshot finally has data and allows me to use them, I do not understand what is wrong in the construction of my Future Builder.
Here is the code of my function where I get the data and the construction of the Future Buider.
Function where data are obtained.
Future<List<Guide>> getGuidesList() async {
var guidesProvider = Provider.of<GuidesProvider>(context, listen: false);
Checkuser data = await ManagerDB().checkuser(auth.email);
List<Guide> aux = new List();
Guide guide;
List guides = await guidesProvider.setGuidesFromUser(data);
if (guides != null) {
for (var i = 0; i < guides.length; i++) {
await guides[i].get().then((DocumentSnapshot guides) {
guide = Guide.fromMap(guides.data(), guides.reference.path);
aux.add(guide);
});
}
if (this.mounted) {
setState(() {});
}
print('Guias cargadas correctamente');
return aux;
} else {
print('Lista vacia');
return aux;
}
}
Fragmento de Funcion donde creo mi FutureBuider.
return Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
appBar: appBar,
drawer: DrawerNavigationMenu(
getContext: widget.getcontext,
),
body: FutureBuilder<List<Guide>>(
future: getGuidesList(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListCourses(
getContext: widget.getcontext,
items: snapshot.data,
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
if (this.mounted) {
setState(() {});
}
Delete this part. You are unnecessarily rebuilding your scaffold and re-calling FutureBuilder. Let FutureBuilder take care of processing the future and rebuilding the scaffold for you.
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
I want to search for a word in a big text file that is local (not calling any HTTP or API).
I'm using it with FutureBuilder because only the opening of the text file is async (the rest isn't)
but the opening itself is very fast.
I want to render CircularProgressIndicator, while searching, but it seems that the moment it finishes opening the file, the CircularProgressIndicator stops, and I have a blank screen for the long searching time.
What can I do to present a loading screen also while doing the regular for loop?
What I have is something like this pseudocode:
Future<Array> searchData() async{
results = [];
someBigTextFile= await getTextFile();
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: searchData(),
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
The Future call should not be in your build function. Make the call to your future and use the result in your build function.
Future<Array> result = searchData();
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: result,
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
Where you make the call for result will depend on the structure of the rest of your code.
Give this a try. With the .then() syntax you can specifically stop sync code from running before a future returns.
Future<Array> searchData() async{
results = [];
await getTextFile().then((someBigTextFile){
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
});
}
You should use isolates to spawn totally new working thread. Something like:
Array searchData( String someBigTextFileContents) {
results = [];
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Future<Array> searchData() async {
someBigTextFileContents= await getTextFile();
return compute( searchData, someBigTextFileContents )
}
Better example on here: https://flutter.dev/docs/cookbook/networking/background-parsing
Basically I use this syntax to use a FutureBuilder:
retur FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
rerurn Text("not access server")
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return Container();
} else {
return Text("Loading");
}
});
Every time I want to make a web request, I have to write all this code again.
I want to optimize this, so I'm thinking of converting the above code into a method where I simply pass a future (http request) parameter and return what my FutureBuilder would return.
I'm trying something like this:
Future generateFutureBuilder(Future<dynamic> future, Widget widget) {
//widget is a Widget to show when it finishes
FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return widget;
} else {
return Text("Loading");
}
});
}
generateFutureBuilder(http.get(myurl), Container(child:Text("finished")))
but it doesn't behave like it normally does.
What I can do?
Although using a method to abstract widgets is OK, it typically is better to create a class that extends StatelessWidget. To fix your current method, you need to make it return FutureBuilder, not Future, and actually add a return statement. Another good change would be changing the use of dynamics to generics for better type safety.
But another plausible solution that will give you a little more control when building your widget is to not abstract away the FutureBuilder itself, but just the snapshot it gives you. Example:
/// Returns a widget to show if needed (either error or loading), or null if success
Widget getFutureSnapshotWidget(AsyncSnapshot snapshot) {
// if has data that is ok:
// return null;
// else:
// return either an error as a Text or a CircularProgressIndicator
}
Then you can call the following in your builder of FutureBuilder:
final widgetToShow = getFutureSnapshotWidget(snapshot);
if (widgetToShow != null) {
return widgetToShow;
}
// Show regular widget here