How to get the root document which a subcollection document belongs to? - firebase

After getting a snapshot for a particular document in a subcollection, I would like to get the associated data to the root document which it belongs to.
For example, there is a collection restaurants and each one has a products subcollection.
After searching for a particular product of a restaurant, how can I get the restaurant data which the product belongs to? Do I need to make another search or how can I get it from the product snapshot?
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collectionGroup('products').get();
QueryDocumentSnapshot product = snapshot.docs.elementAt(0);

There's actually a very easy way to do this, and it takes advantage of the ref.path of the document. You can split the ref.path string into it's component sections - remember, it's the full "/" separated path to the document, so for example:
restaurants/{restaurantId}/products/{productId}
topCollection/{topdocumentId}/nextCollection/{nextDocumentId}/.../bottomCollection/{bottomDocumentId}
Clearly, the first two segments are the path to the "parent" document - regardless of the depth of the tree (I happen to have a very deep tree structure). You can then fetch the "top parent":
db.collection(topCollection).doc(topdocumentId).get()

To find the parent/restaurant document for product, you'll want to walk up the reference/parent chain:
var restaurantRef = product.reference.parent.parent;
You will still need to get that document with:
var restaurantDoc = await restaurantRef.get();

Related

Firestore How to retrieve document's autogenerated id in the past?

In Firestore, I have added a single document in a subcollection, I don't know its id because it was added in the past, but I know its path. How to get its id ? Should I save its id at the creation?
Thank you
I don't know its id because it was added in the past, but I know its
path.
If with "I know its path" you refer to the Document path, then the Document ID is the last element of the path, in which elements are separated by a slash. For example rootCollectionName/parentDocId/parentCollectionName/docId.
If with "I know its path" you refer to the path of the subcollection, then you could query the subcollection and take the first document, since there is a single doc in the collection.
For example with Dart:
QuerySnapshot querySnapshot = await Firestore.instance.collection("rootCollectionName/parentDocId/parentCollectionName").get();
var documentID = querySnapshot.docs[0].id;

How to get a specific document from QuerySnapshot in Flutter Firestore?

Guys how can i retrieve a specific document from QuerySnapshot.docs?
I once get all documents of a collection.
And after some code, i want to get a document by its ID from that collection.
I want to get that document specifically from this QuerySnapshot since i don't want to be messed with await later in my code.
So if i can get all documents all at once, why would i get every document one by one later with async call which would waste my time?
The thing would look like this:
QuerySnapshot usersSnapshot = await FirebaseFirestore.instance.collection("users").get();
//
//some code
//
DocumentSnapshot userDoc = usersSnapshot.docs.doc("user12345");
I want to get document with id user12345.
Is there any function like this .doc(ID) that i can apply on QuerySnapshot??
No, there is no such method on QuerySnapshot. You have to locate the document you want by iterating the results of the query in the docs property, and check each DocumentSnapshot for the ID you're looking for.
If you want to make that easier for yourself for repeated lookups, you can iterate the docs list, and build a Map of document snapshots keyed by the ID. Query the Map for each document you want.

How to get document with certain fields from firebase to flutter?

I need a flutter query for getting data from firebase;
The query should go to User collection and search every document with UserType = 1 field. There is a "classes" collection in each document and each of "classes" have other documents such as "math", "music" etc. I need to get the document with the field classCode == myInput
QuerySnapshot query1 = await _fireStore.collection("Users").where(["userType"], isEqualTo: 1).get();
I don't know how to move from here.
You have two main options for implement this.
The first is pretty close to how you describe in your question:
Find the user docs for your condition,
For each of those search the classes subcollection for the classCode.
The second approach makes use of a collection group query to:
Find all classes docs (across all the subcollections) with the searched user type.
Possibly then look up the users for those.
The best approach depends on mostly on what results you need. If you results are user documents, the first approach is usually pretty easy. If the results are classes then I'd definitely try the second approach first.

Firebase sets collection but does not retrieve and sees it when i reference it from flutter

I am trying to create a document and collection following each other
with this reference
CollectionReference att = FirebaseFirestore.instance
.collection('attendance/$_currentClass/pieces');
but when it is created it is shown like that image below
and i can't see documents that has name of "_currentClass" variable.
If we create create it by ourselves it creates properly.
Here top one was created by me and bottom one created inside flutter
What am i doing wrong?
Based on your path your CollectionReference to this subcollection is:
CollectionReference att = FirebaseFirestore.instance
.collection('attendance')
.doc(_currentClass)
.collection('pieces');
Document, you are pointing with italic font does not exist, it was deleted, but before that it had a collection, and pointing that documents on that subcollection were not deleted and they still exist.If you create documents with sub-documents and then delete the top level document from an SDK, the delete is not recursive. So while the top-level document is gone, its sub-documents remain.
Since there is no document anymore, the document itself will not show up in query results. If you need to find this document in query results, you'll want to create an empty document as a workaround.

Is it possible to fetch all documents whose sub-collection contains a specific document ID?

I am trying to fetch all documents whose sub-collection contain a specific document ID. Is there any way to do this?
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection. I couldn't figure out how to go backwards to get the parent document ID.
I make the assumption that all the sub-collections have the same name, i.e. enquiries. Then, you could do as follows:
Add a field docId in your enquiries document that contains the document ID.
Execute a Collection Group query in order to get all the documents with the desired docId value (Firestore.instance.collectionGroup("enquiries").where("docId", isEqualTo: "ykXB...").getDocuments()).
Then, you loop over the results of the query and for each DocumentReference you call twice the parent() methods (first time you will get the CollectionReference and second time you will get the DocumentReference of the parent document).
You just have to use the id property and you are done.
Try the following:
Firestore.instance.collection("books").where("author", isEqualTo: "Arumugam").getDocuments().then((value) {
value.documents.forEach((result) {
var id = result.documentID;
Firestore.instance.collection("books").document(id).collection("enquiries").getDocuments().then((querySnapshot) {
querySnapshot.documents.forEach((result) {
print(result.data);
});
First you need to retrieve the id under the books collection, to be able to do that you have to do a query for example where("author", isEqualTo: "Arumugam"). After retrieving the id you can then do a query to retrieve the documents inside the collection enquiries
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection.
There is no way you can do that in a single go.
I couldn't figure out how to go backwards to get the parent document ID.
There is no going back in Firestore as you probably were thinking. In Firebase Realtime Database we have a method named getParent(), which does exactly what you want but in Firestore we don't.
Queries in Firestore are shallow, meaning that it only get items from the collection that the query is run against. Firestore doesn't support queries across different collections in one go. A single query may only use the properties of documents in a single collection. So the solution to solving your problem is to perform two get() calls. The first one would be to check that document for existence in the enquiries subcollection, and if it exists, simply create another get() call to get the document from the books collection.
Renaud Tarnec's answer is great for fetching the IDs of the relevant books.
If you need to fetch more than the ID, there is a trick you could use in some scenarios. I imagine your goal is to show some sort of an index of all books associated with a particular enquiry ID. If the data you'd like to show in that index is not too long (can be serialized in less than 1500 bytes) and if it is not changing frequently, you could try to use the document ID as the placeholder for that data.
For example, let's say you wanted to display a list of book titles and authors corresponding to some enquiryId. You could create the book ID in the collection with something like so:
// Assuming admin SDK
const bookId = nanoid();
const author = 'Brandon Sanderson';
const title = 'Mistborn: The Final Empire';
// If title + author are not unique, you could add the bookId to the array
const uniquePayloadKey = Buffer.from(JSON.stringify([author, title])).toString('base64url');
booksColRef.doc(uniquePayloadKey).set({ bookId })
booksColRef.doc(uniquePayloadKey).collection('enquiries').doc(enquiryId).set({ enquiryId })
Then, after running the collection group query per Renaud Tarnec's answer, you could extract that serialized information with a regexp on the path, and deserialize. E.g.:
// Assuming Web 9 SDK
const books = query(collectionGroup(db, 'enquiries'), where('enquiryId', '==', enquiryId));
return getDocs(books).then(snapshot => {
const data = []
snapshot.forEach(doc => {
const payload = doc.ref.path.match(/books\/(.*)\/enquiries/)[1];
const [author, title] = JSON.parse(atob(details));
data.push({ author, title })
});
return data;
});
The "store payload in ID" trick can be used only to present some basic information for your child-driven search results. If your book document has a lot of information you'd like to display once the user clicks on one of the books returned by the enquiry, you may want to store this in separate documents whose IDs are the real bookIds. The bookId field added under the unique payload key allows such lookups when necessary.
You can reuse the same data structure for returning book results from different starting points, not just enquiries, without duplicating this structure. If you stored many authors per book, for example, you could add an authors sub-collection to search by. As long as the information you want to display in the resulting index page is the same and can be serialized within the 1500-byte limit, you should be good.
The (quite substantial) downside of this approach is that it is not possible to rename document IDs in Firestore. If some of the details in the payload change (e.g. an admin fixes a book titles), you will need to create all the sub-collections under it and delete the old data. This can be quite costly - at least 1 read, 1 write, and 1 delete for every document in every sub-collection. So keep in mind it may not be pragmatic for fast changing data.
The 1500-byte limit for key names is documented in Usage and Limits.
If you are concerned about potential hotspots this can generate per Best Practices for Cloud Firestore, I imagine that adding the bookId as a prefix to the uniquePayloadKey (with a delimiter that allows you to throw it away) would do the trick - but I am not certain.

Resources