My Android app has documents saved in Firestore following the path val documentName = "pets/$userid/$animal/$animalId". Animal can be cat, dog, bird, etc. I want to get all the pets owned by a specific user and then I want to iterate through the list of pets. How do I do that? I haven't been able to find an example. So basically I have
firestore.document("pets/$userId").get().addOnSuccessListener {
// now what?
}
There is no way to loop over all collections in the client-side SDKs, and also no way to query across collections with different names.
So the only real option is to perform a separate query for each subcollection:
firestore.document("pets/$userId/cat").get()...
firestore.document("pets/$userId/dog").get()...
firestore.document("pets/$userId/bird").get()...
And then you'd merge them client-side.
I'd also consider whether you really need these subcollections though. You can also store all of them in a single subcollection, and use a field to differentiate between animal types. That is a simpler model, which probably will work fine for this use-case.
You can't query across multiple subcollections in a single query. A query has to target a single collection or subcollection (or a collection group query that targets all collections with the exact same name). Also Firestore client SDKs can't list subcollections under a document.
You should probably restructure your data to support the queries you intend to make, which means putting the entire list of things in a single subcollection with a predictable name.
Related
is there any way to get documents from firebase by list of IDs?
I have this list:
val IDs= listOf("id1","id2","id3","id4")
and I want to get all those documents without looping through them.
something like this, if possible:
Firebase.firestore.collection("users").documents(IDs).get()
The Firestore client libraries don't have the notion of batch reads which would allow to retrieve a set of documents given it's ids. Nonetheless, you can still make queries that filter on the document id by using the FieldPath.documentId() object. This value translates to a special sentinel that allows queries on the document id so you can write a query like below:
Firebase.firestore.collection("users")
.whereIn(FieldPath.documentId(), listOf("id1", "id2", [...], "id10"))
There's one restriction to note with this approach. The whereIn filter accepts at most 10 values. If you need to retrieve more it would be necessary to make several queries.
Alternatively, the approach suggested by Brettski of making a bunch of single document reads would work too.
That isn't possible in Firestore. Id's can only be accessed directly through a collection/document request.
I don't know Kotlin, though in JavaScript I would perhaps approach it by creating an array of functions for each of the documents, then using a Promise.all() to retrieve them at once. It is, imo a downside of Firestore where you can't query for multiple Id's.
In my chat application, I store the participants of a chat as their UIDs in a Map so I can so I can do queries like this:
.whereEqualTo("participantUIDs.$currentUserUid", true)
.whereEqualTo("participantUIDs.$partnerUid", true)
The problem is when I try to use this with orderBy
.whereEqualTo("participantUIDs.$currentUserUid", true)
.orderBy("lastMessageSentTimestamp")
I have to create a custom index. But this index will contain that specific user UID and I can't create an index for every user in my app. How can I circumvent this problem?
You can order the documents on the client after an unordered query. This should not be very taxing on the client app when the number of documents is less than 10,000.
Regarding:
I can't create an index for every user in my app.
That's definitely not an option, as there are some limitations when it comes to Cloud Firestore indexes:
https://firebase.google.com/docs/firestore/quotas#indexes
However, even if you manage to stay below these limits, that's not an option to manually create an index for each and every user that joins your app.
In my opinion, for your particular use-case, you should consider augmenting your data structure to allow a reverse lookup. Meaning that you should create a participantUIDs collection where you should keep the lists for each user. This technique is called denormalization and is a common practice when it comes to NoSQL databases like Cloud Firestore or Firebase Realtime Database.
But remember, there is "no perfect database structure":
What is the correct way to structure this kind of data in Firestore?
It's a little old, but I think this video might also help:
https://www.youtube.com/watch?v=u3KwKQddPoo
More info regarding why you need an index:
Why does this firestore query require an index?
P.S. You can also rely on Firebase Realtime Database when Cloud Firestore may become a little expensive. Both work really well together.
Info:
Array or Subcollection for storing events user uploaded
I am using firebase Cloud Firestore for my Android and iOS apps. I am stuck with how to structure my database.
Basically I have an "organization" collection that contains many users, and for every user, I want to save attendance time and leave time for every day.
The problem is I want to generate reports that allow me to get every day-attendance for each user, single-day attendance for all users, and all days attendance for all users.
so I tried this: inside the user I would have an "attendance" collection then each document is Unix timestamp of that day (to make sure that it's unique). then save fields like attend_time and leave_time...etc. the path is like that "/organization/android/users/3PRs42gRFzZQhLKcUhpf4wPMRV43/attendance/1590271200"
then I needed to get attendance for a single day for all users, so I did this: now I have another path "/organization/android/attendance" and inside the attendance, I store the Unix timestamp of the day, then the user ID then his attendance. and now I am saving attendance twice.
but I still can't get attendance for all days for all users!
this would be easy in Relational Database like SQL. any idea how to do it in firebase?
If you want to track attendance across all users, you're looking for a collection group query. This allows you to query documents from all collections named attendance.
Since a query in Firestore can only see data in the collection(s) it queries, you may have to duplicate some data from parent collections (your users and organizations) into each attendance document to allow the query. This type of data duplication is quite normal in NoSQL databases too.
Finally: if you need to perform many ad-hoc queries, you might want to consider using a database for those that is more suited to that use-case. For example, it is quite common to use Firestore to handle the direct-to-client interactions that require scaling to massive number of users, but then use BigQuery for the ad-hoc querying of that data. There is even a Firebase Extension that automatically exports to BigQuery to make this easier.
FirebaseUI-Android provides for indexed queries when using a FirebaseRecyclerAdapter. That works well when accessing Firebase Realtime DB data as explained here: https://github.com/firebase/FirebaseUI-Android/blob/master/database/README.md#using-firebaseui-with-indexed-data
But what about accessing Firestore data? It seems that FirestoreRecyclerAdapter does not support indexed queries via setIndexedQuery(keyref, dataref, ...) How can I maybe execute the data query manually inside the RecyclerView?
UPDATE: several people have claimed that Firestore doesn't need indexed queries anymore due to the build-in indexing with makes the schema design much easier than it was with Firebase Realtime DB. Well, I disagree.
Lets say I have a bunch of related items and transactions which I want to query by item and by transaction.
I can't make the transactions a subcollection of items as this would make it impossible to retrieve a list of all transactions. I also can't replicate the item data as redundant fields in each transaction since the data is mutable.
Given these constrains I am stuck with making items and transactions two independent collections which need to be combined again inside the app:
items: {
"item1": {
"name": "some editable label",
[more fields]
}
transactions: {
"trans1": {
"itemid": "item1"
[more fields]
}
"trans2": {
"itemid": "item1"
[more fields]
}
}
FirebaseRecyclerAdapter.setIndexedQuery would have allowed me to retrieve a list of transactions (key) and the corresponding item record with each transaction (data). Firestore indexing, while powerful, does not help in this situation.
There is no setIndexedQuery() method beneath FirestoreRecyclerOptions class because there is no need for a such a method. From the offical documentation regarding indexes,
Cloud Firestore requires an index for every query, to ensure the best performance. All document fields are automatically indexed, so queries that only use equality clauses don't need additional indexes.
If you need a query that is using more than one propoerty, you need to manually create a new index from the Firebase console. To create an index programmatically is not supported yet by Firestore.
Edit:
I can't make the transactions a subcollection of items as this would make it impossible to retrieve a list of all transactions.
That's correct, there are no collections beneath collections or documents beneath documents. But you can use the follwing structure:
Collection -> Document -> Collection
But remember, using nested collections and documents it's now a common practice in Firestore. Let asume you have transactions beneath items. In the Firebase Realtime Database, if you had a object nested within another object, every time you would have wanted to query your database to display only the items, the enitire Items object would have downloaded together with the Transactions object, ending to spend more bandwith. But when it comes to Collections and Documents that's not the case anymore.
I don't understand the use case of your app because there because there is less information but there is much easier in Cloud Firestore to create relations between collections and documents. If you have time, you can take a look on one of my tutorials on how to create a Firestore database structure.
Even if you are using Firebase Realtime database or Cloud Firestore database, denormalization is normal. So don't be afraid to duplicate data.
I have a Collection of Lists in which I store a bunch of documents. Each document contains a few details and a Collection of users. I store the users within the users Collection using the uid as a key and a boolean as a value. This means that only those users will be able to read that particular list. This is my database structure.
In my code I use this query:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference listsRef = rootRef.collection("Lists");
Query query = listsRef.orderBy("date").whereEqualTo("users."+uid, true);
And I get the following error:
FAILED_PRECONDITION: The query requires an index. You can create it here: ...
If I go to the Console to create the required index, everything works fine. But is there a possibility to create the indexes automatically, because I cannot create manually an index for each user that joins my app. Is there a problem if I have a few sorting indexes for each user?
Is there another workaround?
Thanks in advance!
Cloud Firestore automatically creates indexes on individual fields of your documents. Complex indexes that span multiple fields can (currently) only be created manually in the Firebase console.
Also note that not all queries need a unique index, as Firestore may be able to merge indexes (especially) for more complex queries. This can drastically reduce the number of unique indexes you need.
If you find that you need to create indexes programmatically, you typically should consider augmenting your data structure to allow a reverse lookup. For example, in your scenario, I'd add a userLists collection where you keep the lists for each user.