Query specific field in Firestore snapshot - firebase

I created this stream that gets a snapshot of a specific document. Inside the document theres an array called tripAttractions that I want to build into a list. The question is, how do I access this specific array from the snapshot?
Stream<QuerySnapshot> getAttractions(BuildContext context) async* {
Firestore.instance
.collection('trips')
.document(documentId)
.snapshots(includeMetadataChanges: true);
}
The list shows how I'm trying to access the snapshot tripAttractions data but this doesn't work.
ListView.builder(
itemCount: snapshot.data['tripAttractions'].length,
itemBuilder: (BuildContext context, int index) =>
tripAttractionsCards(
context, index, snapshot.data['tripAttractions']),
);
Array inside the firestore document

Most likely you're just missing the array accessor in the item builder:
ListView.builder(
itemCount: snapshot.data['tripAttractions'].length,
itemBuilder: (BuildContext context, int index) =>
tripAttractionsCards(
context, index, snapshot.data['tripAttractions'][index]),
);

Ok the issue was that incorrectly called QuerySnapshot. I'm supposed to use DocumentSnapshot since I'm calling a document directly. Also I had to add yield* to return the data from firestore.
Stream<DocumentSnapshot> getAttractions(BuildContext context) async* {
yield* Firestore.instance
.collection('trips')
.document(documentId)
.snapshots(includeMetadataChanges: true);
}
I was able to troubleshoot this by checking if the snapshot was sending any data by adding an if statement inside the streamBuilder.
if (!snapshot.hasData) return Text("Loading");

Related

Flutter Firebase Cloud Firestore get stream of list of documents by their ids

Hey guys I have two top level collections, a user and a tabs collection. Within each user document I have an array field of document ids with each element in the array representing a document in the tabs collection (sort of like a reference or a pointer to that document) and I would like to listen to real time changes in the users/document with the list of ID's and to listen for changes to the corresponding documents in the tabs collection.
Below is my code so far and there are two issues with it. The stream isn’t working client side - I’m not receiving an updated snapshot when I update the document in cloud firestore. Also in order for me to get a list of tabIds I am currently making a query to cloud firestore which returns a stream however, the whereIn operator only accepts a list of objects so I’m not to sure how to handle that.
data model screenshot
Stream<List<Tab>> get tabs {
var tabIds = _db.collection('users').doc(user.uid).snapshots().where((event) => event.get('tabs'));
return _db
.collection('tabs')
.where(FieldPath.documentId, whereIn: tabIds)
.snapshots()
.map((list) =>
list.docs.map((doc) => Tab.fromJson(doc.data())).toList());
}
You can't directly listen to documents based on their ids, but you can add a field called docId (the value should be the id of the document) to each document then listen to collection with this where condition.
List listOfDocIds = []; // this is the list of your docIds
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('users')
.where('docId', whereIn: listOfDocIds),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasError) return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
List<Map<String, dynamic>> data =
snapshot.data.docs.map((e) => e.data()).toList();
print(data);
// you can build your widget here
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(data[i]['name']),
// TODO: change 'name' to name of field in users document
);
},
);
},
),

Get only documents in a FireBase collection that have an id matching with documents in a separate collection

I have the following database structure:
users
|_____user01
|_____groups
|_____0002
|_____0004
|_____0006
groups
|_____0001
|_____0002
|_____0003
|_____0004
|_____0005
|_____0006
I currently have a StreamBuilder() that creates a list with all the entries it finds in root/groups, so it creates a list of 6 elements.
I need it to create a list using the entries in root/groups as it's doing now, but only those that have an id that matches the IDs of entries present in root/users/user01/groups, so it should create a list with 3 elements (0002, 0004 and 0006).
I can't wrap my head around how to achieve that.
This is my StreamBuild() :
StreamBuilder(
stream: FirebaseFirestore.instance.collection("groups").snapshots(),
builder: (context, snapshots) {
if (snapshots.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshots.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot documentSnapshot =
snapshots.data.documents[index];
return Dismissable(
child: Card(
)
)
Should I make a firebase query using where() that excludes documents IDs not shared by the two collections and feed the results to the StreamBuilder?
I would recommend you to use a FutureBuilder for every document inside the list you are creating using StreamBuilder and rendering using ListView.builder.
Create a future that matches each and every document in the list with documents inside the sub-collection of user i.e, groups.
Future<bool> matchDocs(String id)async{
final result = await FirebaseFirestore.instance.collection('users').doc(FirebaseAuth.instance.currentUser.uid).collection('groups').doc(id).get();
return result.exists;
}
Inside itemBuilder function return a FutureBuilder.
StreamBuilder(
stream:
FirebaseFirestore.instance.collection("groups").snapshots(),
builder: (context, snapshots) {
if (snapshots.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshots.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot documentSnapshot =
snapshots.data.documents[index];
return FutureBuilder<bool>(
future: matchDocs(
snapshots.data.documents[index].data()['id'].toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data) {
return Dismissible(key: null, child: null);
}
}
return Container();
}
);
}
);
}
}
)
Note: I did not have any idea about your exact data models, so you
should be careful while implementing this solution.

Flutter Firebase not returning correct queried documents

I want to find all the documents in the 'communities' collection, where the user's id is contained in the 'members' array.
But my firebase query is just returning ALL the documents in the 'communities' collection instead of just the ones I queried.
Stream<QuerySnapshot> get userCommunities {
return communityCollection.where('members', arrayContains: uid).orderBy('lastActive', descending: true).snapshots();
}
...
StreamBuilder(
stream: DBService().userCommunities,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData)
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data.documents[index]['name']));
});
else
return Container();
},
),
testCommunity2 does not contain the user's id in members but my app displays both communities in the listview
Fixed it! I found that my uid was returning null in return communityCollection.where('members', arrayContains: uid)

flutter: Retrieving multiple document id present inside a collection of firebase

I want to get the id of every document in a collection. i tried this code but it return a single document id unlimited times. Can you please suggest a correction or a alternative
getrequest() {
linkref.document(uuid).
collection("Requests").getDocuments().then((value) async{
value.documents.forEach((doc) {
user.add(doc.documentID);
});},);
return ListView.builder(
itemBuilder: (BuildContext cntxt, int index){
return Text(user[index]);
});
}
there is a collection inside a document and inside this collection i have other document. i want to retrieve all documents id
This is screenshot of my firestore
Currently, you have just one document in firestore. I would suggest you add multiple documents and then test this command.You can use snapshot to get multiple documents as suggested by Sandeep using
Firestore.instance
.collection('Links')
.document(docID)
.collection('Requests')
.snapshots();
You can retrieve multiple documents with one request by querying documents in a collection. By default, Cloud Firestore retrieves all documents that satisfy the query in ascending order by document ID, but you can order and limit the data returned. To retrieve the documents conditionally using where() and then use get
More can be found in the documentation here
To use ListView builder, I would suggest using StreamBuilder something like this:
StreamBuilder<DocumentSnapshot>(
stream: Firestore()
.collection('homepage')
.document(widget.user.uid)
.collection('h')
.document(todaysDate())
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
snapshot.data.data.forEach((index, individualDetail) {
cardDetails[index] = individualDetail;
});
}
return cardDetails == null
? CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) {
return HomepageCards(
user: widget.user,
cardDetails:
cardDetails[cardDetails.keys.toList()[index]],
);
},
itemCount: (cardDetailKeys == null
? 0
: cardDetailKeys.length),
);
},
)
This is a snippet from my code but the StreamBuilder would look similar for you too.
Firestore.instance
.collection('Links')
.document(docID)
.collection('Requests')
.snapshots();

How to efficiently access a firestore reference field's data in flutter?

Using similar code as flutter's firestore example, suppose there is a reference field stored in a snapshot document, called: document['userRef'].
First of all, how do I access the data of userRef? Using document['userRef'].get().data or document['userRef'].get().username I wasn't able to access the data. (NoSuchMethodError: Class 'Future<DocumentSnapshot>' has no instance getter 'data')
I also tried using document['userRef'].get().then(...) but getting the error: type 'Future<dynamic>' is not a subtype of type 'String'
Even if .then would work, wouldn't it then look up the same reference again for each message? Here the database is updated in realtime, but it's unnecessary to make the same lookup for multiple messages in the ListView.
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
// document['userRef'] exists here
return ListTile(
title: Text(document['message'] ?? '<No message retrieved>'),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
}
}
Edit:
I was able to fetch the nested data using FutureBuilder, though not sure how efficient it is. (Wouldn't this possibly send loads of redundant requests to Firebase?)
Creating a widget for the nested data, where document['userRef'] exists:
FutureBuilder(
future: userData(document['userRef']),
builder: (BuildContext context,
AsyncSnapshot<dynamic> uData) {
return Text(uData.data['username']);
},
);
And the userData function looks like this:
Future<dynamic> userData(DocumentReference user) async {
DocumentSnapshot userRef = await user.get();
return userRef.data;
}
Sticking to the Firebase and Flutter way, it is possible to use a Streambuilder inside a Streambuilder. That is, instead of using a FutureBuilder for the nested data, which makes you wait for each .get request.
(The code is untested, but the principle is tested.)
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
Map UserSnapshot = Map(); // create a variable for accessing users by id
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('users').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> UsersSnapshot) {
// process usersnapshot from list to map
UsersSnapshot.data.documents.forEach((userRecord) {
//print(optionRecord.documentID); // debug
UserSnapshot[userRecord.documentID] = userRecord;
});
// user data can be accessed as soon as there is a reference field or documentID:
// UserSnapshot[document['userRef']]['userName'}
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection('messages').snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> MessagesSnapshot) {
if (!MessagesSnapshot.hasData) return const Text('Loading...');
final int messageCount = MessagesSnapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document =
MessagesSnapshot.data.documents[index];
// document['userRef'] exists here
// UserSnapshot[document['userRef']]['userName'} is accessible here
return ListTile(
title:
Text(document['message'] ?? '<No message retrieved>'),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
});
}
}

Resources