Using fields in a document for another query - flutter

I have 2 collections, one called Timeline and one called Posts. The first one is very simple, having 2 fields: 'PostId' and 'OwnerId', while the second one is a little bit more complex but it is not important for the purpose of my question.
Using 'OwnerId' and 'PostId' I can get a specified post in the collection Posts.
What I want to do is getting all the docs in timeline of a specified user, for each doc use it to get the post infos in Posts collection, and order the posts in descending timestamp, but I can't find a smart and effective way to do so.
To get all the docs of a specified user in Timeline I write:
QuerySnapshot snapshot = await timelineRef
.doc(currentUserID)
.collection('timelinePosts')
.get();
And to get a specified post from Posts collection I write:
QuerySnapshot snapshot = await postsRef
.doc(ownerId)
.collection('userPosts')
.doc(postId)
.get();
How can I mix these two to get the result I want? Thank you

There is no concept of a server-side join in Firestore, nor is there a way to filter the documents returned based on information in documents in another collection. All Firestore queries can do is evaluate the literal data in the candidate documents (through an index) and filter based on that.
So you will either have to duplicate the data to filter on in each userPosts document, or perform a so-called client-side join - with the latter being the most reasonable option for this use-case as far as I can see.
You'll end up with individual get() calls for the documents, or a bunch in in queries on the FieldPath.documentId() you get from timelinePosts, and then merge the results in your application code.

At the moment I found a solution that is not very elegant but at least is working:
QuerySnapshot snapshot = await timelineRef
.doc(widget.currentUser.userID)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.get();
List<TimelineItem> timelineItems =
snapshot.docs.map((doc) => TimelineItem.fromDocument(doc)).toList();
List<PostWidget> postsTemp = [];
for (var element in timelineItems) {
DocumentSnapshot documentSnapshot = await postsRef
.doc(element.ownerId)
.collection('userPosts')
.doc(element.postId)
.get();
postsTemp.add(PostWidget(Post.fromDocument(documentSnapshot)));
}
I added timestamp field to my timelinePosts, created a class to contain the data from the first query, and then I did a second query based on the parameters I got on the first one for each doc.
Hopefully I'll find a more efficient solution but at the moment I use this

Related

How can I get a collection inside a QuerySnapshot

On the explore page, I get() the entire users collection to create a user list and search results. Inside each of those user documents is a collection posts that I also need to get to create a GridView of each post. I want to reuse that users collection QuerySnapshot instead of fetching each posts collection again to save money. Is this possible?
Here is my current function:
void fetchUsers() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
final docs = result.docs.asMap();
docs.forEach((index, value) {
final profile =
ProfileObject.fromJson(value.data() as Map<String, dynamic>);
usersList.add(UserSearchResult(profile, value.id));
/// Below is the code for getting the posts, not working, need ideas
final QuerySnapshot postsResult = value.get('posts');
final posts = postsResult.docs.asMap();
posts.forEach((index, value) {
final post = Post.fromJson(value.data() as Map<String, dynamic>);
postsList.add(post);
});
});
print(usersList);
print(postsList);
}
Here is the structure of my Firestore:
users
uid (doc)
posts (collection)
info (fields)
uid (doc)
posts (collection)
info (fields)
It is not possible to call a collection to get all sub-collections. You should restructure your database to include sub-collection data in document itself. You can use a map or list for that. But remember, calling everything in one go may end up in slow performance and you might end up losing your customers. So the best way is to include the info in every posts' documents. That way, you won't loss your money and user won't feel lag in performance.
It is not possible. You fetch a document, then fetch the (sub)collection under it.
Subcollection data are not included in the initial document snapshots because Firestore queries are shallow. There shouldn't be any cost savings that you can do there?
See the similar Q&A:
Firestore: Get subcollection of document found with where

DocumentID search in Firestore with a List

Can i search a Firestore DocumentID with a List<String>?
I am trying to search through my collection with some selection of documentID in a List. The List will consist of few String. Can I search through the Firestore collection using this?
This is the List:
List<String> _selectedBusStop = List<String>();
This is the code I used in finding the DocumentID based on the list that is in here.
Future <void> saveRoute(_selectedBusStop) async{
Firestore.instance.collection('markers').where('BusstopName', isEqualTo: _selectedBusStop)
.snapshots().listen((location) {
if(location.documents.isNotEmpty){
for (int i = 0; i < location.documents.length; i++){
initRoute(location.documents[i].data, location.documents[i]);
}
}
});
setState(() {
});
}
I am using where and isEqualTo or is this approach wrong? Any idea how to make it work for this part? Thank you in advance for your help.
Update:
This is how my Firestore looks like:
The List have some of the BusstopName but not all of it. I do not want to retrieve all the data from the Firestore just the one that is in the List. Sorry for causing so many misunderstanding.
Use the whereIn operator, like this:
Future <void> saveRoute(_selectedBusStop) async{
Firestore.instance.collection('markers').where('BusstopName', whereIn: _selectedBusStop)
.snapshots().listen((location) {
if(location.documents.isNotEmpty){
for (int i = 0; i < location.documents.length; i++){
initRoute(location.documents[i].data, location.documents[i]);
}
}
});
setState(() {
});
}
Assuming your documents have a unique id stored in the field BusstopName and also the documents actual id matches the content of this field, you have 2 possibilities.
(1) .where query
query data with collection("markers").where("BusstopName", "=", "yourBuststopId").
this returns a querySnapshot Object, on which you can call .size to check if there were any documents with that Id found (could be more than 1 if you have an inconsistent database).
(2) .doc query
query data with collection("markers").doc("yourBuststopId")
this returns a documentSnapshot Object, on which you can call .exist to check if the document actually exsists.
In both cases you need to do 1 query per Id, because Firestore queries only support equality and range operations. See this similar SO question. I would suggest to do the queries asynchronously, otherwise the time to execute will increase with the size of the array.
If you are concerned about costs, you only get billed for the results that actually return documents that exist.
you might also try this:
FirebaseFirestore.instance
.collection('markers')
.where('BusstopName', arrayContainsAny: ['Utar Bus Stop', 'Garden Bus Stop'])
.get()
.then(...);
Taken from the examples documentation

Deleting a Firestore entry based on FIFO

I'm trying to create a script that deletes a record from a Firestore collection using a FIFO (First In First Out approach).
So if there are three matching results in the collection, the script should take the first one added and just delete that one (leaving the remaining two). My code is:
_firestore
.collection('myCollection')
.where('uid',
isEqualTo: _auth.currentUser.uid)
.where('field',
isEqualTo: widget.field)
.orderBy('Posted', descending: false)
.limit(1)
.get()
.then((querySnapshot) {
querySnapshot.docs
.forEach((documentSnapshot) {
_firestore
.collection('myCollection')
.doc(documentSnapshot.id)
.delete();
});
});
(Just to note: 'Posted' is the date the entry was added) Unfortunately this doesn't work, and all three results remain in the collection.
If though I use this script instead, then all three results are removed from the collection:
_firestore
.collection('myCollection')
.where('uid',
isEqualTo: _auth.currentUser.uid)
.where('field',
isEqualTo: widget.field)
.get()
.then((querySnapshot) {
querySnapshot.docs
.forEach((documentSnapshot) {
_firestore
.collection('myCollection')
.doc(documentSnapshot.id)
.delete();
});
});
An example of an entry in my collection is as follows:
So I know the logic, connection, fields etc... are all correct, but why does the first example not work?
Have you check you logs? I am pretty sure that Firebase is throwing an error saying that your collection need indexes with a link.
Just follow the link and the instruction to build the indexes. Once complete you should be able to do what you are looking for.
More info about Indexes: https://firebase.google.com/docs/firestore/query-data/indexing
and Simple queries / Coumpound queries: https://firebase.google.com/docs/firestore/query-data/queries

CollectionReference to DocumentReference

I'm trying to perform a firestore transaction in Flutter.
Therefore I need to pass the documentReference.
There is no problem, when accessing the "lesson" document based on the identifier.
The issue is when fetching the active user pass. The where I'm trying to use returns the Query, and the .reference() returns the CollectionReference (which is not accepted by the transaction method).
How can I get the reference to the document basing on the value of its field.
To spice it up: DB rules don't allow me to read ALL the passes. I've got only access to those, that have my UserID (double checked - working).
DocumentReference lessonRef =
await _db.collection('lessons').document(lesson.identifier);
CollectionReference passRef = await _db
.collection('passes')
.where('userID', isEqualTo: user.identifier).reference();
You can get the DocumentReference of the Queried documents like so:
Firestore.instance
.collection('passes')
.where('userID', isEqualTo: user.identifier)
.snapshots()
.listen((reference) =>
data.documents.forEach((doc) => /*do what you want with doc.reference*/));
(I'm not an expert in Flutter and I could't test the code, but I based the answer in the API reference)

I am trying to use a field from the collection 'profile' say 'username' to filter the records from the main collection 'orders'

Is there a way to achieve this?
i have tried to assign the entry to a local variable but it doesn't work with crudmethods.
getData() async {
String userId = 'userId';
Firestore.instance.collection('user').document(userId).snapshots();
var snapshot;
var userDocument = snapshot.data;
String _myAddress = userDocument["address"];
return Firestore.instance
.collection('letters')
.where("source Box", isEqualTo: _myAddress)
.snapshots();
}
Yes, you should be able to use a document in the query of another document.
For this, you need to create a reference for one collection and use it in another one.
The below code is an example that you can give it a try adapting further for your case, but that I believe should help you.
// Create references to the profile and orders collections
var profilesRef = db.collection("profile");
var ordersRef = db.collection("orders");
// Create a query against the collection.
var query = ordersRef.where("username", "==", ).doc("username").get();
In the documentation Perform simple and compound queries in Cloud Firestore, there is more information and example of queries that should help you.
In addition to that, this below post from the Community can provide you some insights on how to achieve this type of query as well.
How can I get specific document data from firestore querysnapshot?
Let me know if the information helped you!