How to populate firebase reference type in flutter - firebase

This is how my firebase firestore data look like
You can see that I have the person data field which is a reference type.
And this is how I retrieve data from my flutter dart,
Query<Map<String, dynamic>> persons = FirebaseFirestore.instance
.collection('fl_content')
.where('_fl_meta_.schema', isEqualTo: 'talks');
This query can get only the parent level of data, but not person's detail values.
In person, I have fields like Name, Age, PhotoUrl.
How can I get and populate those values together?
The result I want is
Result
------
id: "N48xxxxx"
saidWhat: 'HiThere'
Name: 'PersonName'
Age: 30
PhotoUrl: 'url'
I used Flamelink for backend firebase cms.

A bit beside the point of your question, but I would recommend using sub collections (https://firebase.google.com/docs/firestore/data-model) for the person's data, as you are going to make your life harder by mixing the document "types" in the fl_content collection.
You'll also want to be careful not to use the reference as a foreign key
That being said, see below a potential solution to answer your question.
If your goal is only to get read data, you can just map trigger the call to Firestone from the reference:
List<DocumentSnapshot> persons = await FirebaseFirestore.instance
.collection('fl_content')
.where('_fl_meta_.schema', isEqualTo: 'talks')
.get();
persons = await Future.wait(persons.map((_) => _.data().person.get()));
(didn't test, but the idea is there). This one makes sense as it is only fetching the persons you need, BUT it is expensive in terms of http calls and Firestone roundtrips.
An other option, would be to fetch all talks, and all persons, and map them together. You might be fetching unused data which is a bit of a bad practice, but it will be less expensive than the first option (I assumed that your schema would be persons:
List<DocumentSnapshot> talks = await FirebaseFirestore.instance
.collection('fl_content')
.where('_fl_meta_.schema', isEqualTo: 'talks')
.get();
List<DocumentSnapshot> persons = await FirebaseFirestore.instance
.collection('fl_content')
.where('_fl_meta_.schema', isEqualTo: 'persons')
.get();
Map<String, dynamic> mappedTalks = talks.map((_) => ({..._.data(), person: persons.firstWhere((__) => __.id == _.person.id, orElse: () => null)}));
(also didn't test, but the idea is also here ;) )

In NoSQL databases like Firebase it is common standard to create collections holding redundant data, each of them tailored to (one or more) specific READs.
One does not combine several READs; instead on creates a collection which allows to get all the relevant data with one single READ.
The reason for this is speed of access over the Internet. This speed is traded for the increasing amount of storage needed for holding redundant data.
The redundant data is usually kept in sync with the help of Firebase Functions, which listen to changes in collection A and subsequently update elements in collection B.
Example
Say, one has these collections:
User
Messages
To get the messages of a user in a performant way, one creates a tailored redundant collection which combines the other two (and leaving out data that is not necessary for the specific READ / Use Case like date of birth or address):
UserMesssages

Related

getting number documents of a collection in Firebase in dart flutter

im trying to retrieve the number of courses created, the courses created will show in "Courses" collection.
this is the code i used
String course = '';
FirebaseFirestore.instance
.collection("Courses")
.get()
.then((QuerySnapshot querySnapshot) {
course = querySnapshot.docs.length.toString();
});
this is a screenshots of my firebase
Collections don't know how many documents they contain so to get the count you have to retrieve all of the documents to then get a count. That could be time consuming as well as a lot of reading (cost).
The simple solution is to keep the count in another collection or at a known document location within the collection.
Generically speaking, suppose we have three collections, Courses, Users and Locations and each one could have 0 to thousands of documents.
Users
user_0
...some fields
user_1
...some fields
Courses
course_0
...some fields
course_1
...some fields
Locations
location_0
...some fields
location_1
...some fields
As previously mentioned, if there are a limited number of documents simply reading Users (for example) and getting the count of the documents from the snapshot works and is simple. However, as Users grows so does the document count and cost.
The better and scalable solution is to keep anther collection of counts
Document_Counts
users_collection
count: 2
courses_collection
count: 2
locations_collection
count: 2
Then when you want the number of users, simply read the Document_Counts/users_collection document and get the count from the count field.
Firestore has a lighting fast and simple increment and decrement function so as a document is added to Users, for example, increment that same count field.
With Cloud Firebase 2.0, there is a new way to count documents in a collection. According to reference notes, the count does not count as a read per document but a metaData request:
"[AggregateQuery] represents the data at a particular location for retrieving metadata without retrieving the actual documents."
Example:
final CollectionReference<Map<String, dynamic>> courseList = FirebaseFirestore.instance.collection('Courses');
Future<int> countCourses() async {
AggregateQuerySnapshot query = await courseList.count().get();
debugPrint('The number of courses: ${query.count}');
return query.count;
}

Update document value in Firestore Cloud with Flutter, having only a unique key value of such document

I'm new to Cloud Firestore but already made some CRUD operations but now I'm really stuck with this thing.
Each document inside the 'tiposFrota' collection has a key 'nome:' with a unique value, no document will have the same value for the 'nome:' key.
The problem is, whenever the user adds another 'Truck' to some other collections I need to increment the value of 'qtde:' by one, and when they remove 'Truck' the program will increment the number by -1, working as a counter.
I managed to create an operation to update a key value, but only when you have the document id, but in this case the id is autogenerated because they may add or remove standard values from the 'tiposFrota' collection.
FirebaseFirestore.instance
.collection('controladores')
.doc('contadores')
.update({'numFrota': FieldValue.increment(1)}),
I'm really stuck with this, if anyone could please help.
Thanks!
Woah, managed to find a solution by myself, this post Get firestore collections based on values in array list in flutter.
Since the 'nome:' value is unique for each document inside the 'tiposFrota' collection I can use the .where statement as a filter for said document, get the snapshot with all the documents (but only getting one, obviously) and use the 'forEach' method to create a function using the '.id' parameter when calling the document.
FirebaseFirestore.instance
.collection('tiposFrota')
.where('nome', isEqualTo: carMake)
.get()
.then((querySnapshot) {
querySnapshot.docs.forEach((element) {
FirebaseFirestore.instance
.collection('tiposFrota')
.doc(element.id)
.update({
'qtde': FieldValue.increment(1)});
});
}),

Flutter Firebase: Retrieve a list of documents, limited to IDs in an array?

I'm working on a Flutter app where each user can create projects, and share projects with other users. I've created a 'shares' collection, where each user's ID is a document, and within that document, all project IDs that have been shared with that user are collected like so, with a boolean that represents whether or not the share has been accepted yet:
Next, I created a collection of the projects themselves, like so:
Now, I'd like to query the 'projects' collection and return only the projects that are in a given user's 'shares' list. First off, how can I get each document in the share list's ID? And secondly, is it possible to compare that ID to the contents of a List using a .where() clause?
I've been trying something like this, but to no avail:
Stream<List<Map<String, dynamic>>> getListOfProjectsForUser({#required List<String> shares}) {
var ref = _firestore.collection('projects');
return ref
.where(shares, arrayContains: ref.id)
.snapshots()
.map((QuerySnapshot snapshot) => snapshot.docs.map((DocumentSnapshot doc) => doc.data()).toList());
}
I also tried this:
Stream<List<Map<String, dynamic>>> getListOfProjectsForUser({#required List<String> shares}) {
var ref = _firestore.collection('projects');
return ref
.where(shares, arrayContains: FieldPath.documentId)
.snapshots()
.map((QuerySnapshot snapshot) => snapshot.docs.map((DocumentSnapshot doc) => doc.data()).toList());
}
Is what I'm trying to do even possible? I've been messing with this for two days and my head's exploding. Any help would be greatly appreciated. Thanks in advance.
You'll need two operations.
Read the document for the user, to determine the list of project IDs.
Perform a in query for the project documents matching those IDs. The in operator accepts up to 10 IDs, so if you have more than 10 projects you'll need multiple queries and merge the results in your application code.
var citiesRef = db.collection("projects");
citiesRef.where(FieldPath.documentId, arrayContains: ['project1id', 'project2id']);
Also see:
The FlutterFire documentation for the where(field, whereIn:) operation
The FlutterFire documentation for the FieldPath.documentId field
First off, how can I get each document in the share list's ID?
For this, you're required to actually query the entire collection. You can iterate the results to collect the IDs of each document. There is no easy way to just get a list of IDs directly from web and mobile client code. See: How to get a list of document IDs in a collection Cloud Firestore?
And secondly, is it possible to compare that ID to the contents of a List using a .where() clause?
If you have a list of document ID strings in memory that could be any length, you will need to perform a query filtering projects for "projOwner" for each individual ID. There are no SQL-like joins in Firestore, so you can't simply join the two collections together with a single query.
Here's how you do a single one - you have to call out the name of the field to filter on:
firestore
.collection("projects")
.where("projOwner", isEqualTo: id)
If you have 10 or less share IDs in the list, you can use an "in" query to find matches from projects, and it will not work with any more.
firestore
.collection("projects")
.where("projOwner", whereIn: listOfIds)
So, if you think the list could ever be larger than 10, you should just start by performing individual queries for each share ID.
if 'arrayContains' is not working try 'whereIn'.
var citiesRef = db.collection("projects");
citiesRef.where(FieldPath.documentId, whereIn: ['project1id',
'project2id']);

How to fetch only documentIDs [Firebase-Flutter]

In my firebase's database I have Users and every user has multiple stores that he ordered from and every store has multiple orders.
To fetch all the orders for specific store I write the following query and it works fine.
QuerySnapshot result = await Firestore.instance
.collection('users')
.document(userID)
.collection('Stores').
document(storeID).getDocuments();
However, I do not need that. I just need the stores that the user ordered from. In other words, I need list of storeIDs for specific user.
Here is my code but it doesn't work.
QuerySnapshot result = await Firestore.instance
.collection('users')
.document(userID)
.collection('Stores').getDocuments();
I just want the IDs
It's not possible with web and mobile clients to get only the IDs without the entire set of documents and their data. If you want a list of all IDs for all documents in a collection or subcolleciton, you're going to have to query that collection as you currently show in the second example. You can easily extract the IDs from the returned documents in the QuerySnapshot. But you are still paying the cost of a document read for each document in the collection, and waiting for all of their contents to transfer.
If you want, you can create your own backend endpoint API to use a server SDK to query for only the IDs, and return only the IDs, but you are still paying a document read for document.

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