How can you nest StreamBuilders in Flutter?
Solution 1
I have done something similar simply using nested StreamBuilders
. Depending on how you want your Widget
s organized, you can create streams within the outer StreamBuilder
. Based on your clarifying comments, this is one possibility:
@override
Widget build(BuildContext context) {
var habits = Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.snapshots();
return StreamBuilder<QuerySnapshot>(
stream: habits,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Text("Loading habits...");
return ListView(children: snapshot.data.documents.map((document) {
var query = Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.document(document.documentID)
.collection("history")
.where('day', isGreaterThanOrEqualTo: start)
.where('day', isLessThanOrEqualTo: end)
.snapshots();
return StreamBuilder<QuerySnapshot>(
stream: query,
builder: (context, snapshot) {
if (!snapshot.hasData) return Text("Loading...");
// right here is where you need to put the widget that you
// want to create for the history entries in snapshot.data...
return Container();
},
);
}).toList());
},
);
}
Solution 2
Try merging your streams with something like Observable.zip2(stream1,stream2,zipper)
or Observable.combineLatest2(streamA, streamB, combiner)
.
For more info, check this post
Anthony Sette
Apparently, this user prefers to keep an air of mystery about them... sometimes. These are my opinions, get triggered... Top five languages are as follows (and probably changed between the time I am writing this and the time you are reading it): Python, JS, Dart, TypeScript, C++ PHP is the worst language ever invented. Nevermind Java might be worse. Vue.js is the best Web Framework. Flutter is the best cross-platform development framework. Now time for a stupid joke since I'm writing this at 3:00 am after a long coding session: Why does Python live on land? Because its above C level. Ok, Goodnight my doods. I just realized all of this amazing profile work will have to be rewritten when I graduate college.
Updated on December 13, 2022Comments
-
Anthony Sette over 1 year
I have 2 Streams that I need to combine to build a widget, but unlike other questions I have seen I need to nest my streams.
I have a stream that gets a collection of documents from Firestore, and a stream that depends on data from the first to get a subcollection of documents. I would like to combine these into one stream, but they need to be nested since each document has its own subcollection of documents.
Stream 1 (Gets a collection of habits from FireStore):
Stream<List> getHabits(){ final Stream<QuerySnapshot> documents = Firestore.instance .collection("users") .document('VtL1sxOoCOdJaOTT87IbMRwBe282') .collection("habits") .snapshots(); Stream<List> data = documents.map((doc) { List data; final documents = doc.documents; ///Maybe this would work to get history of each doc? for(int i = 0; i < documents.length; i++){ ///not sure what to do getHistory(documents[i].documentID, DateTime.utc(2019,7,7), DateTime.now()); } data = documents.map((documentSnapshot) => documentSnapshot).toList(); return data; }); return data; }
Stream 2 (Called in Stream 1, Takes
DocumentID
as a parameter, gets sub-collection of documents):Stream<List> getHistory(String id, DateTime start, DateTime end) async* { await for (QuerySnapshot querySnapshot in Firestore.instance .collection("users") .document('VtL1sxOoCOdJaOTT87IbMRwBe282') .collection("habits") .document(id) .collection("history") .where('day', isGreaterThanOrEqualTo: start) .where('day', isLessThanOrEqualTo: end) .snapshots()) { List history; final documents = querySnapshot.documents; history = documents.map((documentSnapshot) => documentSnapshot).toList(); yield history; } }
Any help on how I can combine these streams in a nested format into one stream to be used with
StreamBuilder
in flutter would be appreciated!'EDIT I am not sure if I am working in the right direction or not but I have tried to implement the solution from spenster and this is what I have at the moment in addition to the functions above.
StreamBuilder<List>( stream: getHabits(), initialData: [], builder: (context, snapshot) { List<UserHabit> habits = []; List<Widget> test = List.generate(snapshot.data.length, (index){ List<History> history = []; DocumentSnapshot doc = snapshot.data[index]; return StreamBuilder( stream: getHistory(doc.documentID, DateTime.utc(2019,7,7), DateTime.now()), builder: (context, snapshot) { if (snapshot.hasError) return new Text('Error: ${snapshot.error}'); switch (snapshot.connectionState) { case ConnectionState.waiting: return new Text('Loading...'); default: if(!snapshot.data.isEmpty){ //history collection exists for(int i = 0; i < snapshot.data.length; i++){ //add to history history.add(History( day: snapshot.data[i]['day'].toDate(), dateCompleted: snapshot.data[i]['dateCompleted'].toDate(), morning: snapshot.data[i]['morning'], afternoon: snapshot.data[i]['afternoon'], evening: snapshot.data[i]['evening'], anytime: snapshot.data[i]['anytime'], )); } } habits.add(UserHabit( name: doc['habit'], color: doc['color'], icon: doc['icon'], repeat: doc['repeat'], daily: doc['daily'], weekly: doc['weekly'], monthly: doc['monthly'], time: doc['time'], history: history, )); print(habits); //returns each iteration of assembling the list return Text("i dont want to return anything"); } }, ); } ); print(habits); //returns empty list before anything is added return Column( children: test, ); }, ),
The Class for UserHabits and History can be shared, but they are just basic classes that assign types and allow easy access.
-
Anthony Sette almost 5 yearsThank you! I'll give it a shot and if it works I will mark your answer as correct! I appreciate the time you took to answer it!
-
Anthony Sette almost 5 yearsJust out of curiosity is there any way I can just not return anything in
getHistory
StreamBuilder
? I need to use a list ofhistory
data to see if things were completed or not, but I am only building one widget per item ingetHabits()
-
Anthony Sette almost 5 yearsI am also getting the following error
The element type 'Set<StreamBuilder<List>>' can't be assigned to the list type 'Widget'.
-
spenster almost 5 yearsIf you only want one document from your history colection, then you can use Firestore to select it (say using .limit(1) on your query), then build just a single widget. That should also incidentally eliminate the other error you're seeing too. I'll see if I can update the answer...
-
Anthony Sette almost 5 yearsHere I am going to update the question so you can see what I mean. I made some progress but I cannot access a variable that I modified in the second streambuilder outside of it. There is one stream that gets the habits, then the other gets the history for each habit. For the application I am working on I need to check to see say how many times something was completed in the last week before showing a widget. There is only one widget being returned per habit, but the widget requires all the history to assemble it if that makes sense.
-
Anthony Sette almost 5 yearsIf you have a chance please check my update so you can better understand my intentions. If I could use habits at the second print location I think this solution would work but I can't because StreamBuilder is asynchronous. Which is why I wanted to nest the streams before using StreamBuilder but I wasn't sure if that was possible.
-
spenster almost 5 yearsPlease see my update. All you have left to do there is create your widget based on the various history entries retrieved in the second
StreamBuilder
. -
Anthony Sette almost 5 yearsI like where this is headed, but the second stream depends on the
documentID
of an item from the first stream. And the second stream is different for every item from the first. I need them to be nested in some manner rather than zipped -
Anthony Sette over 4 yearsI think I managed to get this method to work but I feel like there should be a way to nest streams without needed to nest streambuilders. Or firebase should make it easier to access sub-documents haha. Thank you so much for your time, I will mark this correct! Been working on this one dilemma for about a week.