Handling multiple futures in Build - flutter

This is my build:
#override
Widget build(BuildContext context) {
return FutureBuilder<Position>(
future: _init,
builder: (context, snapshot) {
...
final Position position = snapshot.data!;
return FlutterMap(
...
layers: [
...
MarkerLayerOptions(
markers: markers, //this is the future list
),
],
);
});
}
Now, markers is a Future and I build it with this methods:
late Future<List<Marker>> markers;
#override
void initState() {
...
markers = getMarkers();
}
Future<List<Marker>> getMarkers() async {
List<Marker> markerTemp = [];
for (var friend in friendsList) {
DocumentSnapshot document = await locationRef.doc(friend).get();
if (document.exists)
markerTemp.add(Marker(...));
}
return markerTemp;
}
So when I run my application I get an error saying that markers is not initialized. How can I have my list ready when called in the build method?
I tried things like nested FutureBuilder or using Future.wait([item1,item2]) but since I'm newbie to this language I'm having troubles implementing it the right way probably

Try using FutureBuilder in some way similar to this:
return FutureBuilder<List<Marker>>(
future: markers,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
if (snapshot.hasError)
return Text("${snapshot.error}");
// You can access the list here, use this newly created list
List<Markers> markerList = snapshot.data as List<Marker>;
return FutureBuilder<Position>(...)
});

I believe FutureBuilder will solve your problem. try this:
#override
Widget build(BuildContext context) {
return FutureBuilder(
initialData: null,
future: Future.wait(getMarkers()),
builder: (context, snapshot) {
...
},
);
}
it's that simple. if something goes wrong just make sure your getMarkers() actually return a list of Futures and you'll be alright

Related

Flutter: get bool value from snapshot

I am trying to show an on-boarding screen so to do so I call an async function where I get a Future<bool> weather this is the first time the user launched the app. Since its a Future value I have decided to use FutureBuilder.
Here is part of the code of what I am trying to do:
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: FutureBuilder(
future: firstLaunch, //Future<bool> value
builder: (context, snapshot) {
if (snapshot.hasData) {
// What I would like to do is use either one of these lines
// return _firstLaunch ? const OnboardingScreen() : const WelcomePage();
// return snapshot.data ? const OnboardingScreen() : const WelcomePage();
} else {
return const WelcomePage();
}
},
),
);
}
The issue is that I am unable to use _firstLaunch within the FutureBuilder since its still considered a future value and snapshot.data does give me the correct value when I print it but the type is AsyncSnapshot<Object?> so I also cant seem to use it.
Does anyone have suggestions on how to solve this or possibly know of a better way to show the on-boarding screen?
Prefer documentation:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Try this:
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: FutureBuilder<bool>(
future: firstLaunch, //Future<bool> value
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
return snapshot.data ? const OnboardingScreen() : const WelcomePage();
} else {
return const WelcomePage();
}
},
),
);
}
Use a bool inside has data
if (snapshot.hasData) {
bool check = snapshot.data;
return check ? const OnboardingScreen() : const WelcomePage();
}

How do I get all the data in a collection in flutter firestore and display it on the screen?

I wrote the following code
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
dynamic books = getData();
return Scaffold(
body: Column(
children: <Widget>[
Text(books.toString()),
],
),
);
}
}
getData() async {
QuerySnapshot querySnapshot =
await FirebaseFirestore.instance.collection('books').get();
return querySnapshot;
}
But the screen shows Instance of 'Future' How can I use the data in the firestore?
Since the data is loaded from Firestore, it won't be available immediately and in fact your getData function returns a Future<QuerySnapshot>. You'll need to use a FutureBuilder object in the UI to handle the asynchronous nature of the data.
Something like this:
FutureBuilder(
future: getData(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(),
}else{
return ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
return Text(data['full_name']), // 👈 field from your document
})
.toList()
.cast(),
}
} else if (snapshot.hasError) {
return Text(snapshot.error!);
}
return CircularProgressIndicator();
}),
)
This way:
dynamic books = await getData();
Also this will open up other problems, as the build method is not async.
Use a Controller-Model-View Pattern to avoid these: The controller fetches model (data) and the view displays the model.
Or you use a StreamBuilder inside the Widget to show live data from Firebase.

How to pass parameters into the future instance of Futurebuilder?

I am trying to pass parameters into the future instance but I encounter the "error instance member can't be accessed in an initializer" for _getData at future: _getData even though I have initialized it in initState.
class _DisplayListState extends State<DisplayList> {
late Future<QuerySnapshot> _getData;
Future<QuerySnapshot> getData(String value)async{
return await FirebaseFirestore.instance
.collection('x')
.where('a', isEqualTo: value)
.get();
}
#override
void initState(){
super.initState();
_getData = getData(widget.value);
}
Widget displayList = FutureBuilder(
future: _getData,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
return Container();
},
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: displayList,
);
}
}
I remove the the parameters for getData thinking that I should simplify the problem first and tried future: getData() but I encoutered the same error.
The only way that I don't encounter the error is if I do this - future: FirebaseFirestore.instance.collection('x').where('a', isEqualTo: 'something').get() for FutureBuilder. But this means I cannot pass parameters and I would prefer to use a function/variable for this over the long line of code.
future: FirebaseFirestore.instance.collection('x').where('a', isEqualTo: value).get() where value=widget.value is initialized in initState will give me the same error.
You can use a method for displayList instead of a variable.
You can change this:
Widget displayList = FutureBuilder(
future: _getData,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
return Container();
},
);
to this:
Widget getDisplayList() => FutureBuilder(
future: _getData,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
return Container();
},
);
And in the Scaffold's body, use the method like this:
body: getDisplayList()

Why does my async method run twice in Flutter?

I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}

FutureBuilder reloading whenever the BottomNavigationBarItem is changed

I'm using FutureBuilder on a screen with BottomNavigationBar. But whenever I click on a different tab and come back, FutureBuilder reloads everything again. I'm already using AutomaticKeepAliveClientMixin, I'm having trouble saving getLessons() so I don't have to load it again. Can someone help me?
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lesson>>(
future: getLessons(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
} else if (snapshot.connectionState == ConnectionState.waiting) {
} else {
return Container();
}
});
}
This is my getLessons():
Future<List<Lesson>> getLessons() async {
String url = "...";
http.Response response = await http.get(url);
var data = json.decode(response.body);
(...)
return lessons;
}
How can I maintain the state so as not to update?
// Create instance variable
Future myFuture;
#override
void initState() {
super.initState();
// assign this variable your Future
myFuture = getLessons();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lesson>>(
future: future, // use your future here
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
} else if (snapshot.connectionState == ConnectionState.waiting) {
} else {
return Container();
}
});
}
Credit to CopsOnRoad
The problem is that I was calling the screens without using PageView. I started the 4 screens outside the build() and called them all within a PageView, now it works.
body: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_index = index;
});
},
children: [_container1, _container2, _container3, _container4],
),
If you replace the PageView with PreloadPageView, the FutureBuilders will not be called again
just install preload_page_view here