I want to add 2 collections in Firestore in React Native.
Like JOIN can be used to add 2 tables. Is there any alternative for JOIN in Firestore to add collections?
I want to add these 2 collections users and users_2
How can I do this? Please help
At the time of writing it is not possible to query documents across collections in Firestore (it is apparently a feature that is on the roadmap however, see this recent blog post https://cloud.google.com/blog/products/databases/announcing-cloud-firestore-general-availability-and-updates -see bullet point "More features coming soon"-).
So that means that you'll have to issue two queries (one for each table, to get all the collection docs) and join/combine their results in your front end.
Another approach would be to duplicate your data (which is quite common in NoSQL world) and create a third collection that contains copies of all the documents.
For this last approach you could use a Batched Write as follows (in Javascript):
// Get a new write batch
var batch = db.batch();
var docData = {email: 'test#gmail.com', fullname: 'John Doe'}
// Set the value of doc in users collection
var usersRef = db.collection('users').doc();
batch.set(usersRef, docData);
// Set the value of doc in the allUsers collection (i.e. the third collection)
var allUsersRef = db.collection('allUsers').doc();
batch.set(allUsersRef, docData);
// Commit the batch
return batch.commit().then(function () {
// ...
});
Related
I'm working on the Flutter app where users can save multiple addresses. Previously I used a real-time database and it was easier for me to push data in any child with a unique Id but for some reason, I changed to Firestore and the same thing want to achieve with firestore. So, I generated UUID to create unique ID to append to user_address
This is how I want
and user_address looks like this
And this is how it's getting saved in firestore
So my question Is how I append data with unique id do I have to create a collection inside users field or the above is possible?
Below is my code I tried to set and update even user FieldValue.arrayUnion(userServiceAddress) but not getting the desired result
var uuid = Uuid();
var fireStoreUserRef =
await FirebaseFirestore.instance.collection('users').doc(id);
Map locationMap = {
'latitude': myPosition.latitude,
'longitude': myPosition.longitude,
};
var userServiceAddress = <String, dynamic>{
uuid.v4(): {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
}
};
await fireStoreUserRef.update({'user_address': userServiceAddress});
If I use set and update then whole data is replaced with new value it's not appending, so creating a collection is the only solution here and If I create a collection then is there any issue I'll face?
You won't have any issues per se by storing addresses in a separate collection with a one-to-many relationship, but depending on your usage, you may see much higher read/write requests with this approach. This can make exceeding your budget far more likely.
Fortunately, Firestore allows updating fields in nested objects via dot notation. Try this:
var userServiceAddress = {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
};
await fireStoreUserRef.update({'user_address.${uuid.v4()}': userServiceAddress});
I am using Cloud Firestore as my database and I have collections of users where are stored basic information about user such as id, name, last name, email, company id.
Also I have collection of companies and in each company I have collection of tasks.
In each task I have one user assigned from collections of users (user data is replicated, so I have same data for that user as in collection users)
The problem is when I update user (change name or email...) from collection users because data is replicated that data is not changed in collection of tasks for that specific user.
Is there any way that using firestore when user from collection users is updated to automatically update it in collection of tasks?
This is quite a standard case in NoSQL databases, where we often denormalize data and need to keep these data in sync.
Basically you have two possible main approaches:
#1 Update from the client
When you update the "user" document, update at the same time the other documents (i.e. "tasks") which contain the user's details. You should use a batched write to do so: A batch of writes completes atomically and can write to multiple documents.
Something along the following lines:
// Get a new write batch
var batch = db.batch();
var userRef = db.collection('users').doc('...');
batch.update(userRef, {name: '....', foo: '....'});
let userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId1');
batch.update(userTaskRef, {name: '....'});
userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId2');
batch.update(userTaskRef, {name: '....'});
// ...
// Commit the batch
batch.commit().then(function () {
// ...
});
Note that you need to know which are "the other ("tasks") documents which contain the user's details": you may need to do a query to get these documents (and their DocumentReferences).
#2 Update in the back-end via a Cloud Function
Write and deploy a Cloud Function that is triggered when any "user" document is updated and which takes the value of this "user" document and update the "tasks" documents which contain the user's details.
Like for the first approach, you also need, in this case, to know which are "the other ("tasks") documents which contain the user's details.
Following your comment ("Is there any option to reference to another table or put foreign key?") here is a Cloud Function that will update all the ("tasks") documents that have their DocumentReference contained in a dedicated Array field taskRefs in the "user" doc. The Array members are of data type Reference.
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const name = newValue.name;
const taskRefs = newValue.taskRefs;
const promises = taskRefs.map(ref => { ref.update({ name: name, foo: "bar" }) });
return Promise.all(promises);
});
You would most probably set the value of this taskRefs field in the "user" doc from your frontend. Something along the following lines with the JS SDK:
const db = firebase.firestore();
db.collection('users').doc('...').set({
field1: "foo",
field2: "bar",
taskRefs: [ // < = This is an Array of References
db.collection('tasks').doc('....'),
db.collection('tasks').doc('....')]
});
If I want to write to the database from my client side, I can inject an AngularFirestore instance and generate an id automatically using createId():
const individualId = this.angularFirestore.createId();
But if I want to do the same thing in a cloud function, using the Firestore admin API, I can't find an equivalent operation. I can create a Firestore instance in a cloud function by running
const db = admin.firestore();
However, the object that is created has no createId() function available.
Is there an equivalent to createId() that I can use within a cloud function?
I understand from this issue and this article that "AngularFirestore.createId() generates a new id from a symbolic collection named '_'".
If you want to mimic this behaviour in a Cloud Function, you could use the doc() method of a CollectionReference without specifying any path. You will get a DocumentReference, and then you can use the id property to get the "last path element of the referenced document".
Something like the following:
const db = admin.firestore();
const docRef = db.collection('_').doc();
const newId = docRef.id;
Note that, as explained in the issue referred to above, it is a bit weird to "use a generic collection instead of an actual collection" to generate an id, because you would normally use the collection in which you want to create a new Document. But this is not a problem, according to this comment from James Daniels (who is a Firebaser), since the Firestore auto-generated ID is "just a random string and doesn't take the path into consideration at all".
in JavaScript, for the new Firebase 9 (January 2022). In my case I am developing a comments section:
const commentsReference = await collection(database, 'yourCollection');
await addDoc(commentsReference, {
...comment,
id: doc(commentsReference).id,
date: firebase.firestore.Timestamp.fromDate(new Date())
});
Wrapping the collection reference (commentsReference) with the doc() provides an identifier (id)
Is there a way to fetch document after documentID like
private fun fetchCollectoionnAfterDocumentID(limit :Long){
val db = FirebaseFirestore.getInstance()
var query:Query = db.collection("questionCollection")
.startAfter("cDxXGLHlP56xnAp4RmE5") //
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
query.get().addOnSuccessListener {
var questions = it.toObjects(QuestionBO::class.java)
questions.size
}
}
I want to fetch sorted questions after a given Document ID. I know I can do it using DocumentSnapShot. In order to fetch the second time or after the app is resume I have to save this DocumentSnapshot in Preference.
Can It be possible to fetch after document ID?
startAfter - > cDxXGLHlP56xnAp4RmE5
Edit
I know I can do it using lastVisible DocumentSnapshot . But I have to save lastVisible DocumentSnapshot in sharedPreference.
When app launch first time 10 question are fetched from questionCollection. Next time 10 more question have to be fetched after those lastVisible. So for fetching next 10 I have to save DocumentSnapshot object in sharedPreference. Suggest me a better approach after seeing my database structure.
And one more thing questionID is same as Document reference ID.
There is no way you can pass only the document id to the startAfter() method and simply start from that particular id, you should pass a DocumentSnapshots object, as explained in the official documentation regarding Firestore pagination:
Use the last document in a batch as the start of a cursor for the next batch.
first.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
=// Get the last visible document
DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
// Construct a new query starting at this document,
Query next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible) //Pass the DocumentSnapshot object
.limit(25);
// Use the query for pagination
}
});
See, here the lastVisible is a DocumentSnapshot object which represents the last visible object. You cannot pass only a document id. For more information, you can check my answer from the following post:
How to paginate Firestore with Android?
It's in Java but I'm confident you can understand it and write it in Kotlin.
Edit:
Please consider defining an order of your results so that all your pages of data can exist in a predictable way. So you need to either specify a startAt()/startAfter() value to indicate where in the ordering to begin receiving ordered documents or use a DocumentSnapshot to indicate the next document to receive, as explained above.
Another solution might be to put the document id into the document itself (as a value of a property) and order on it, or you can use FieldPath.documentId() to order by the id without having to add one.
You can also check this and this out.
There is one way to let startAfter(documentID) works.
Making one more document "get", then using the result as startAfter input.
val db = FirebaseFirestore.getInstance()
// I use javascript await / async here
val afterDoc = await db.collection("questionCollection").doc("cDxXGLHlP56xnAp4RmE5").get();
var query:Query = db.collection("questionCollection")
.startAfter(afterDoc)
.orderBy("questionID", Query.Direction.DESCENDING)
.limit(limit)
A simple way to think of this: if you order on questionID you'll need to know at least the value of questionID of the document to start after. You'll often also want to know the key, to disambiguate between documents with the same values. But since it sounds like your questionID values are unique within this collection, that might not be needed here.
But just knowing the key isn't enough, as that would require Firestore to scan its entire index to find that document. Such an index scan would break the performance guarantees of Firestore, which is why it requires you to give you the information it needs to perform a direct lookup in the index.
I am trying to write a cloud function that will keep track of the amount of Documents in the Collection. There isn't a ton of documentation on this probably because of Firestore is so now.. so I was trying to think of the best way to do this.. this is the solution I come up with.. I can't figure out how to return the count
Document 1 -> Collection - > Documents
In Document 1 there would ideally store the Count of Documents in the Collection, but I can't seem to figure out how to relate this
Let's just assume Document1 is a Blog post and the subcollection is comments.
Trigger the function on comment doc create.
Read the parent doc and increment its existing count
Write the data to the parent doc.
Note: If your the count value changes faster than once-per-second, you may need a distributed counter https://firebase.google.com/docs/firestore/solutions/counters
exports.aggregateComments = functions.firestore
.document('posts/{postId}/comments/{commentId}')
.onCreate(event => {
const commentId = event.params.commentId;
const postId = event.params.postId;
// ref to the parent document
const docRef = admin.firestore().collection('posts').doc(postId)
return docRef.get().then(snap => {
// get the total comment count and add one
const commentCount = snap.data().commentCount + 1;
const data = { commentCount }
// run update
return docRef.update(data)
})
});
I put together a detailed firestore aggregation example if you need to run advanced aggregation calculations beyond a simple count.