Firestore best practice for refering to document - android

I am trying to show to user items, he participated in (for example liked posts). Now I store in user document (in users collection) IDs of documents from another collection (posts) and when I want to show them in recycler view firstly I get IDs from user document. Then I get all posts by IDs. Is there any workaround, where I would be able to store user ID in subcollection of post document and then get query of all liked/commented/whatever posts by user? So user document will not have reference to post's IDs and in posts collection I am able to do something like:
Query ref = from db.collection("posts") get all posts where post.likedBy == user;
I do no like idea of putting all users who liked the post into post document - user downloads all ids.
posts (collection)
-postID (post document)
-authorID, ... (fields)
users (collections)
-userID (user document)
-string[] idsOfPosts (fields)

You should use Subcollections as your data model.
Documents in subcollections can contain subcollections as well,
allowing you to further nest data. You can nest data up to 100 levels
deep.
Also you can use a collection group query to retrieve documents from a collection group instead of from a single collection. The link provides you with sample code snippets in different languages.
EDIT:
Based on the use case you have provided in the comments:
I would say the way you are describing your data model to get all posts liked by a user, it would need a query inside a query. Not sure if it's even feasible or efficient.
Here is my suggestion:
Build your data model similar to the following
This way running the following query (I'm using NodeJs) would give you all posts liked by user1.
let postsRef = db.collection('posts');
const user1 = postsRef.where('Liked', 'array-contains',
'user1');
let query1 = user1.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
Output:
EDIT: (11/12/2019)
Based on what you have described in the comments, here is an idea that might solve your issue:
Instead of having a list of the Users who liked the post, you can have a reference to a Document that contains the list of users. You can reference to as many Documents as you wish.
Example:
The Documents can be even in a different Collection.

Related

Joining a room using invite code in flutter

I'm trying to create a simple game application using flutter. It has a page where users can enter an invite code and press a button to join the room. I have multiple rooms with different names on firebase and each one has a unique inviteCode. How can I write a function to check through all the rooms if the enteredCode matches any of the rooms' invite codes?
This is what I've tried so far:
Future<bool> _checkInviteCode(String enteredCode) async {
// reference to the Firestore
final firestore = FirebaseFirestore.instance;
//get the invite code collection
final querySnapshot = await firestore.collectionGroup('inviteCode').get();
// check if the entered code matches any of the invite codes in the Firestore collection
if (querySnapshot.docs
.any((doc) => doc.data()['inviteCode'] == enteredCode)) {
print("Code matched!");
return true;
} else {
print("Invalid code");
}
return false;
}
There is no way in the Firestore API to query across multiple collections with different names. The only options are to query a single collection, or to query all collections with one name using a collection group query.
So you will either have to query each collection (name) individually and merge the results in your application code, or you'll have to adapt your data model to fit the use-case (which is quite common in NoSQL databases).
For example, consider creating a collection inviteCodes where you (also) store the codes using the invite code as the document ID and then include a field that points to the document path for the user (or whatever document type you have) that holds that invite code. Having such a table would not only make this use-case simpler, but it will also immediately ensure that only one user can hold each invite code.

How do I retrieve firebase data based on the key in a nested child?

So suppose I am building an app that lets users manage trips. When a trip is created , any number of users can be added in it. I want this data to be stored in a single place and then for each user be able to retrieve the trips that that person is included in. My data looks like this:
-trips
-<trip-id>
-title
-budget
-etc
-people
-<person-id>
-name
-uid
-<person-id>
-name
-uid
-<trip-id>
-
-
-
This trip will contain all the trips created by all the users. To show any person their trips, I want to retrieve only the lists that person exists in.
This is what I've tried to do including other similar approaches.
rootReference.child("trips").orderByChild("uid").equalTo(FirebaseAuth.instance.currentUser.uid).addValueEventListener(object:ValueEventListener){
override fun onDataChange(snapshot: DataSnapshot) {
//this should only return the trips that current user exists in.
}
}
I have checked the documentation for searching and filtering on firebase but there is nothing that show filtering based nested keys. One particular example is this. I understand it perfectly. If for example I try to filter my trips based on the main attributes like title, budget, it works, but not when I use an attribute of a nested child.
What other approach can I use to filter based to nested keys or should I structure the data differently? Any help is greatly appreciated.
Firebase Realtime Database queries operate on a flat list of child nodes directly under the path that you query.
So the value you order/filter on has to be at a fixex path under each immediate child node. Since that isn't the case for your uid, you can't query across all trips for the UID of all users of those trips.
You can query across one trip for a UID of a user (and then get back that user), or you can query across all trips for properties of the trip itself, such as its title or budget.
If you want to query across all users on all trips, consider keeping an additional list where you have the UID of the user as the key, and then all their trips under there:
"user_trips": {
"uid1": {
"tripid1": true,
"tripid2": true
},
"uid2": {
"tripid2": true,
"tripid3": true
}
}
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value
Many to Many relationship in Firebase

How to get a document from a Cloud Firestore collection based on index?

For developing an Android app in Kotlin, I am trying to find a way to retrieve a document from a Firestore collection based on a certain index and I am not sure how to go about it. For example, if my collection has 50 documents, what would be the best way to retrieve the 10th document so that I can populate text views on my app based on the field values of that document?
Thank you for taking the time to answer my question.
When you add documents to Firestore using the CollectionReference#add() method, you'll see a random document ID that is assigned automatically for each document. If you check the Firebase Console or you try to query a collection programmatically, you will not find any order for the containing documents. This means that there is no index involved. So it's not correct to say, hey Firestore, give me the 10th document, because when you query a collection of 50 documents, the 10th document might be the one you are interested in, but if you add/delete documents, the 10th document won't be the same.
You might think, ok, I can provide my own document IDs, starting from 0 - 49, and when I query the collection I can order them by their document IDs. But, this is not an option, since the document IDs are always strings, and when you order strings, the order is lexicographical.
The best solution I can think of, if you need to get a particular document from a collection, is to add a specific field in that document, so it can be identified very easily. For instance, you can add a particular name or ID for each document, and then you can perform a query that looks like this:
rootRef.collection("users").whereEqualTo("name", "Bhavey")
In this way, you can simply identify each one of the documents in your collection.
To accomplish this, will require some setup.
let's say you have a collection of documents and you want to manage them and find the 10th document, Firestore generates unique hash id's that are based on the timestamp making them have an inherent order based on the time they were generated.
All you need to do is manage a dedicated document in the parent of this collection that contains an array of all document id's in the subcollection.
reading this document and it's array would get you an ordered list of documents without incurring the additional reads of the documents as other methods, even listing the ID's requires 1 read each.
on the successful operation of it being added, you can use a Cloud Function onCreate trigger to push the document id to the master array and the same with onDelete. But if cloud Functions are not available or you want to brute force it from the client, you can do the following:
db.collection("users")
.document("uid")
.collection("posts")
.add(city)
.addOnSuccessListener { documentReference ->
db.collection("users")
.document("uid")
.update("index", FieldValue.arrayUnion(documentReference.id))
.addOnSuccessListener { Log.d(TAG, "Index Added!") }
.addOnFailureListener { e -> Log.w(TAG, "Index Error", e) }
}
.addOnFailureListener { e -> Log.w(TAG, "Error source document", e) }
Cloud Functions sources:
trigger_a_function_when_a_new_document_is_created
trigger_a_function_when_a_document_is_deleted
*note: to scale this past 1mb firestore document limits - you will need multiple index documents with a counter and to manage this with Cloud Functions. The end result should double your writes without crippling your reads

Filter and get all sub-collections by using where clause in Firestore

In firestore I have some sub-collections inside each collection just like below:
Collection1 -> id -> Sub-collection -> scId -> Data
So here I want to get all data from sub-collections by the help of the where clause. I tried one query like the below which is failing.
db.collection("Collection1").whereEqualTo("eDate", selectedDate)
.get().addOnSuccessListener(queryDocumentSnapshots -> {
List<Exp> mData = queryDocumentSnapshots.toObjects(Exp.class);
Here eDate is coming under the Data which is inside each sub-collection.
So is there any way to do that or any suggestions on how to do that or about the mistakes which I did here?
Firestore queries are shallow and do not extend above or below the named collection being queried. The query you show here will only return documents immediately within "Collection1". It will not consider any documents in nested subcollections.
If you want documents in a subcollection, you will have to build a CollectionReference to that subcollection, and query it individually. You can't query all subcollection nested under a document at the same time. You can't query across differently-named subcollections at the same time.
If this behavior does not meet the needs of your app, you should consider restructuring your data to do so, or building your app to perform as many queries as needed to get all the documents from among all the collections where they live.

Android's Firestore to join 2 collections into a recycler view

I have a users collection with uId, name, photo
I have a visits collection with uId, userId, location
I have a recyclerview in which I want to show the location with the user name and photo
Can I use the reference field type? If so, how will Firestore know to link visits.userId == users.uId ?
Maybe I first need to query all the visits and then query the relevant user but 2 things:
It means querying a lot of times.
I didn't understand how to collect the joined collection into the adapter, which is based on one query?
Please advice
Thanks
current code
visitsList = db.collection("visitsList");
Query query = visitsList.whereEqualTo("userId",prefs.getString("id","")).orderBy("visitDate", Query.Direction.ASCENDING);
FirestoreRecyclerOptions<AVisit> options = new FirestoreRecyclerOptions.Builder<AVisit>().setQuery(query, AVisit.class).build();
adapter = new VisitsListAdapter(options, VisitsListActivity.this);
RecyclerView rv = findViewById(R.id.rvVisitsList);
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(adapter);
The code is a simple query from the collection, not sure how to get the name and photo from the userId field in that collection.
Can I use the reference field type?
Yes, you can use a reference field.
If so, how will Firestore know to link visits.userId == users.uId ?
Firestore results always comes from a single collection (at the moment). It does not automatically join the document from the users collection when you're reading from the visits collection. You will have to do this yourself.
That indeed means you'll be executing multiple reads, but it's often not nearly as slow as you may think. See Google Firestore - how to get document by multiple ids in one round trip?
Update: To show data from the user profile in a list of visits, there are two main options:
load the additional user document in populateView or with a custom parseSnapshot implementation.
duplicate the relevant user data in the visits collection (which is quite normal in NoSQL databases). Also see Alex' answer here: indexed query with FirestoreRecyclerAdapter.

Categories

Resources