I\'m trying to retrieve posts from a Firestore collection called \"posts\", which contains the post creator\'s userID and post description and this is possible
Posting for those in the future since I spent several hours trying to figure this out - hoping it saves someone else.
First I recommend reading up on Stream
s: https://www.dartlang.org/tutorials/language/streams
This will help a bit and its a short read
The natural thought is to have a nested StreamBuilder
inside the outer StreamBuilder
, which is fine if the size of the ListView
wont change as a result of the inner StreamBuilder
receiving data. You can create a container with a fixed size when you dont have data, then render the data-rich widget when its ready. In my case, I wanted to create a Card
for each document in both the "outer" collection and the "inner" collection. For example, I have a a Group collection and each Group has Users. I wanted a view like this:
[
Group_A header card,
Group_A's User_1 card,
Group_A's User_2 card,
Group_B header card,
Group_B's User_1 card,
Group_B's User_2 card,
]
The nested StreamBuilder
approach rendered the data, but scrolling the ListView.builder
was an issue. When scrolling, i'm guessing the height was calculated as (group_header_card_height
+ inner_listview_no_data_height
). When data was received by the inner ListView
, it expanded the list height to fit and the scroll jerks. Its not acceptable UX.
Key points for the solution:
StreamBuilder
's builder
execution. That means your Stream
needs to contain data from both collectionsStream
can hold multiple items, you want a Stream>
. Comments on this answer (https://stackoverflow.com/a/53903960/608347) helpedThe approach I took was basically
Create stream of group-to-userList pairs
a. Query for groups
b. For each group, get appropriate userList
c. Return a List of custom objects wrapping each pair
StreamBuilder
as normal, but on group-to-userList objects instead of QuerySnapshot
s
What it might look like
The compound helper object:
class GroupWithUsers {
final Group group;
final List users;
GroupWithUsers(this.group, this.users);
}
The StreamBuilder
Stream> stream = Firestore.instance
.collection(GROUP_COLLECTION_NAME)
.orderBy('createdAt', descending: true)
.snapshots()
.asyncMap((QuerySnapshot groupSnap) => groupsToPairs(groupSnap));
return StreamBuilder(
stream: stream,
builder: (BuildContext c, AsyncSnapshot> snapshot) {
// build whatever
});
essentially, "for each group, create a pair" handling all the conversion of types
Future> groupsToPairs(QuerySnapshot groupSnap) {
return Future.wait(groupSnap.documents.map((DocumentSnapshot groupDoc) async {
return await groupToPair(groupDoc);
}).toList());
}
Finally, the actual inner query to get User
s and building our helper
Future groupToPair(DocumentSnapshot groupDoc) {
return Firestore.instance
.collection(USER_COLLECTION_NAME)
.where('groupId', isEqualTo: groupDoc.documentID)
.orderBy('createdAt', descending: false)
.getDocuments()
.then((usersSnap) {
List users = [];
for (var doc in usersSnap.documents) {
users.add(User.from(doc));
}
return GroupWithUser(Group.from(groupDoc), users);
});
}