How to automatically create indexes for Cloud Firestore? - android

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.

Related

Firestore compound query with map fields

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

get Firestore subcollection on Android

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.

How to index documents automatically once added in Firestore according to a Field?

I am making something like a leaderboard using firestore , So I want documents of the Result collection to be indexed automatically in the database not on the client according to TimeDESCENDING so that it will be shown in the client side which is an android app
My firestore database looks like this:
Results:------------------------>(collection)
{bcmskdjdkd}:------------->(document)
Time:22.3------->(Field)
{trhrthrtht}:
Time:21.5
{xfgndrghnt}:
Time:24.6
If your query using the field in question doesn't generate an error, then there is no need to create any indexes. Firestore simply does not perform queries that it can't satisfy efficiently.
Please read the documentation to understand Firestore indexes.
All document fields are automatically indexed, so queries that only use equality clauses don't need additional indexes. If you attempt a compound query with a range clause that doesn't map to an existing index, you receive an error. The error message includes a direct link to create the missing index in the Firebase console.

indexed query with FirestoreRecyclerAdapter

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.

How to return a Firestore field value instead of a document

All of the Firestore data retrieval examples show a full document getting returned. Examples like this:
// Create a reference to the cities collection
CollectionReference citiesRef = db.collection("cities");
// Create a query against the collection.
Query query = citiesRef.whereEqualTo("state", "CA");
My database has 3,000 city objects in it, and I need to get a list of all the unique states in my database. Can someone show me an example of how I can get this information from Firestore? I'm hoping I don't need to download all 3000 documents just to collate this list myself.
You could create a Cloud Function which is triggered by an onWrite event to your cities collection. This function could see if the state already exists in a states collection and, if not, add it.
This way, you'll end up with a states collection which only has one of each state in it. As states are generally a 2 letter code, you could use this code as the document index, to ensure uniqueness in your cloud function writes.
As Frank says, the only way in which you can achieve this is by duplicationg data. This tehnique is named denormalization and for that I'll share you another resourse, which is a tutorial that I personally recomend you to see for a better understanding, Denormalization is normal with the Firebase Database. This tutorial was made for Firebase Realtime database but the same principles are also in Cloud Firestore.
So to solve your problem, you need to create another top level collection in which you need to store only the unique states. But you need to be aware of the fact that every time you add or delete a document, you need to do it twice, once for the cities collection that you already have and second for the newly created collection uniqueStates.

Categories

Resources