Filter Firebase collections on arrays or objects in Firebase Firestore - firebase

I am since a few days looking for a solution for my problem. What i try to do is loading objects from Firestore with a filter lower in the structure.
/meetings (collection)
/meetingId
/people (object)
- uid1:1
- uid2:1
Without a where query, it works fine. But since i want to validate on people in the object. I walk against walls.
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Firestore.instance
.collection('meetings')
.where('people.${user.uid}', isEqualTo: '1')
.orderBy('sessionStart', descending: true)
.snapshots()
.listen((data) {print(data);});
Returns zero results. Also when i self implement a array-contains. It works not. Since i want implement rules on my data structure, i need a filtering.
Every time i execute a query i get a request to create a new index. com.google.firebase.firestore.FirebaseFirestoreException: FAILED_PRECONDITION: The query requires an index. You can create it here.
When i follow the url, i get the suggestion to create the following index:
Collection: meetings
Fields: people.QkrzIBRs3TNHcTbYGSHsZjFPvlj2 ASC sessionStart DESC
What will result in a index per user, what is not my wish.
Can someone help me with this, guide me to the correct solution? I will be thankful for it!

Finally after a lot of trial and error I found the possible solution.
The sortBy made it impossible to execute the query. If i remove it and just only use the where it works perfect.
This was the solution:
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Firestore.instance
.collection('meetings')
.where('people.${user.uid}', isEqualTo: '1')
.snapshots()
.listen((data) {print(data);});
The rule i needed to create was:
match /meetings/{document=**} {
allow read, list: if resource.data.people[request.auth.uid];
}
At the end i was still missing sort functionality. So i resolved in the code after creating Meeting objects from it:
meetings.sort((Meeting from, Meeting to) => from.sessionStart
.toIso8601String()
.compareTo(to.sessionStart.toIso8601String()));
There are probably better solutions, so if you know a better solution, reply please.
Hope this will help other people

Related

Firebase Firestore - Best way to call a bunch of where gets?

I have an array of user emails that I want to then pull from firebase each of the corresponding user documents from a collection where one of the email matches. I don't want to pull the whole collection as it would be expensive. What is the easiest way to do this? I tried for looping over the array with individual gets to firebase but ran into promise issues as I want to do something in Javascript with them right after.
Thanks so much!
based on what i understood from your question i can only think of using await Promise.all() - you can look into this here.
as an example you could pass an array of promises to await Promise.all() so you could do
const res = await Promise.all(
array.map( x => db.collection('users')
.where('email' , '==', x.email).limit(1).get());
mind you that in this example you would still have to process the result as they will return a snapshot not a document ...
Update:
Hey there, i just noticed that you can use in operator in firebase query, which will return the values matching in a given array.
i'm not sure but maybe using it might be suitable in your use-case you can check the documentation here

The method 'updateData' isn't defined for the type 'Query'. Futter, Cloud Firestore

I would like to update data in a document based on a query. Here is my code:
Firestore.instance
.collection("Categories")
.where("userEmail", isEqualTo: "${user?.email}")
.updateData({"category_budget_remaining": _remainingCategoryBudget2})
.then((value){});
I am getting the following error: The method 'updateData' isn't defined for the type 'Query'
after where() you need to get documents then do updating
where(...).getDocuments().then((val)=>
val.documents.forEach((doc)=> {
doc.reference.updateData({...})
});
});
this code selects and updates all documents that goes true for where condition. If you want do it just for 1 document then just add .limitTo(1) before getDocuments().
As you can see from the API documentation, where() returns a Query, and Query doesn't have a method called updateData(). So, what you're seeing here is no surprise.
Firestore doesn't offer a way to bulk update documents like a SQL "update where" command. What you will have to do is execute the query for the documents to change, iterate the documents in the result set, and update each one individually. Yes, it requires a document read for every document to change, and no, there are no alternatives for this.
For me worked by changing
.updateData
to just
.update
My answer is similar to #Habib Mahamadi but this is the correct and only way to acheive this functionality, my answer is little differente as firebase has changed names of functions.
Firestore.instance
.collection("Categories")
.where("userEmail", isEqualTo: "${user?.email}")
.then((value)=> value.docs.forEach((element){element.reference.update({"" : ""})});

What is the difference between getDocuments() and snapshots() in Firestore?

I am a little confused about the difference between these two. My understanding is that getDocuments is a type of Future and seems to get the entire documents according to the query. while snapshots, on the other hand, is a type of Stream and, correct me if I'm wrong, I think it represents the results of the query? I need a more specific explanation of this issue. I will include some code snippets as an example for more clarification
getDocuments()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).getDocuments();
}
snapshots()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).snapshots();
}
So what's the difference?
When you call getDocuments(), the Firestore client gets the documents matching the query from the server once. Since this may take some time it returns a Future<QuerySnapshot>.
When you call snapshots() the Firestore client gets the documents, and then keeps watching the database on the server for changes that affect your query. So if document is written in the users collection that affects your query, your code gets called again. So this returns a stream of QuerySnapshot.
In both cases the results for the entire query are in the QuerySnapshot object.
I highly recommend reading the Firestore documentation on getting data once and on listening realtime updates. While they don't contain Flutter examples, the explanation in there applies equally to the Flutter libraries.
getDocuments():
It's used to provide data once. Cloud Firestore contains collections and inside these collections, you have documents that may contain subcollections or fields mapped to a value. To retrieve any of the doc fields to used it in widget this is used.
snapshots():
It will be called on every data change in your document query. For this StreamBuilder must be used to fetch fields as modified.
In short, it will do the job of setState() where it gives you the response for every modification so that UI can be updated.

Flutter Firebase/Firestore write to multiple tables at once

I'm using Firebase as backend to my Flutter project. I need to write to multiple tables in one transaction. Now I have:
await _databaseReference
.collection('user_data')
.document(uid)
.setData(userData);
await _databaseReference
.collection('company_data')
.document(uid)
.setData(companyData);
But this these are 2 transactions, so 1 could ends successfully but another one could fail and I want to prevent to happening this.
Thanks a lot.
You have to use a Batched Write, which "is a series of write operations to be performed as one unit", as follows:
var batch = _databaseReference.batch();
batch.setData(
_databaseReference.collection('user_data').document(uid),
userData
);
batch.setData(
_databaseReference.collection('company_data').document(uid),
companyData
);
await batch.commit();
Following Rahul Vyas comment below, note that a batch can contain, at the same time, some set, update and delete operations to be executed on different documents (potentially in different collections, as shown above). Look at the Methods section in the doc.

Retrieving snapshots from firestore sub-collection to dart/flutter object

New here. I've googled everything and can't figure this out.
The first two functions below work flawlessly the third always return null immediately without calling my ListFromSnapShot function, which just turns the map into a list. I am assuming
I'm not calling the Firestore correctly but I have no idea how to do this properly.
final CollectionReference customerCollection = Firestore.instance.collection('customers');
final CollectionReference jobCollection = Firestore.instance.collection('jobs');
// get user doc stream
Stream<UserData> get userData {
return customerCollection.document(uid).snapshots()
.map(_userDataFromSnapshot);
}
//Returns only snapshots with matching customer name to StreamProvider object
Stream<List<Job>> get jobQuery {
return jobCollection.where('customerName', isEqualTo: currentCustomer.companyName)
.snapshots()
.map(_jobListFromSnapshot);
}
//The following always returns Null
Stream<List<JobSectionModel>> get jobQuerySections {
return jobCollection.document('Family Dollar').collection('sections')
.snapshots()
.map(_jobSectionListFromSnapshot);
This is my database structure
Base Collection
Subcollection
Any help would be greatly appreciated.
The code above does work, the problem, after several frustrating days, ended up being random spaces in the firestore path. I'm posting this as an answer because after googling this there were many others with a similar problem and I don't want them to go through the same frustration. Firestore will not show this until you literally click on the path name and it will not crash your program, it just returns nothing.
Do yourself a favor and use the .trim() function when adding data and if you do it manually on firestore make sure there's no trailing spaces.
Also I rebuilt the code as follows which also works (without spaces of course)
Stream<QuerySnapshot> getjobQuerySections(BuildContext context) async*{
yield* jobCollection.document('T-MOBILE').collection('whyspaces').snapshots();
Hope this helps someone. Good Luck

Resources