I'm triggering a cloud function with onWrite. Within the onWrite function, I want to do something with the ref ID of the document that was written. How can I grab this? My current code looks something like this, but I don't know how to fill eventID.
exports.syncEvents = functions.firestore
.document('events/{eventID}')
.onWrite((change, context) => {
admin.firestore().collection('users').doc(host).collection('events').doc(eventID).set({
active: true
})
});
change.after is a DocumentSnapshot of the document that changed, after it was written. DocumentSnapshot has a property called id that contains the id of the document. So change.after.id is the document id.
The context object contains the params of the query.
In your example, the context object will look like this:
{
...
params: {
eventID: "whatever-eventId-is"
}
...
}
So you just call get it from the context:
const eventId = context.params.eventId;
Here is more about context:
https://firebase.google.com/docs/reference/functions/cloud_functions.eventcontext
Related
I have a document, and inside of it there is a collection called relatives. In cloud functions, I have onUpdate() listener for this document. Once something is changed, I want to access that collection inside of my document. Also documents in the collection relatives.
Here is how it looks like:
What I have tried
exports.UpdateLocations = functions.firestore.document("users/{userID}").onUpdate((change, context) => {
const userEmail = change.after.data().email;
const prevLocation = change.before.data().userLocation;
const currentLocation = change.after.data().userLocation;
if (prevLocation === currentLocation) return 0;
if (change.after.data().userType.toString() === "Patient") {
const userLocation = change.after.data().userLocation;
const relatives = change.after.data().relatives;
console.log("User's Current Location: " + userLocation);
console.log("Relatives : "+relatives );
}
return;
});
I want to access relatives and its documents. So I can search and compare field and update them on purpose.
To get a subcollection from a DocumentSnapshot, you must first get a DocumentReference to the document for that snapshot, and then find the CollectionReference under that.
In code:
change.after.ref.collection("relatives")
In here:
change.after gives you the DocumentSnapshot of the modified document.
change.after.ref then gives you the DocumentReference of that document, so its location in the database.
change.after.ref.collection("relatives") then gives you the CollectionReference to the relatives subcollection of the document.
So get data from these subcollections you'll have to actually load that data, it is not already included in the change object that is passed to your function.
So if you want to load all relatives for the user that triggered the function, it'd be something like:
let relativesRef = change.after.ref.collection("relatives");
return relatives.get().then((querySnapshot) => {
querySnapshot.forEach((relativeDoc) => {
console.log(doc.id, doc.data().relativeaccount);
});
});
I'm trying to run the following Cloud Function:
exports.getUserData = functions.firestore
.document('UserData/{id}')
.onWrite(async (snap, context) => {
const uid = snap.data.id;
let uData;
console.log("onCreate called. uid="+uid);
await admin.auth().getUser(uid)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
uData = userRecord.toJSON();
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
await admin
.firestore()
.doc('UserData/${uid}')
.set({
userRecord : uData
});
return null;
});
It gets deployed allright, as I can see it in the console. But adding/updating a doc in the collection simply does not trigger the function (nothing shows in log).
A couple of things, as I see a few problems
Seems to me that you want to trigger this function every time there is a new UserData collection. If this is the case, you should use the trigger onCreate. onWrite gets triggered every time a doc is updated, created or deleted.
You function is creating an infinite loop if you use onWrite. You are updating collections which will triggered the same function, over and over.
First argument of the function is not a snapDoc, if you are using onWrite. Check the documentation
This part:
await admin
.firestore()
.doc('UserData/${uid}')
.set({
userRecord : uData
});
'UserData/${uid}' is a string not a template string. Use backtick ` not single quote '
As #renaud-tarnec said, use context.params to get the id parameter
It seems that by doing
exports.getUserData = functions.firestore
.document('UserData/{id}')
.onWrite(async (snap, context) => {
const uid = snap.data.id;
//...
});
you want to assign to the uid variable the value of the {id} wildcard in the 'UserData/{id}'.
For that you should use the context Object, as follows:
const uid = context.params.id;
and as explained here in the doc.
I want to update a document field and I've tried the following code but it doesn't update.
can anyone give me a solution, please?
My Code:
var snapshots = _firestore
.collection('profile')
.document(currentUserID)
.collection('posts')
.snapshots();
await snapshots.forEach((snapshot) async {
List<DocumentSnapshot> documents = snapshot.documents;
for (var document in documents) {
await document.data.update(
'writer',
(name) {
name = this.name;
return name;
},
);
print(document.data['writer']);
//it prints the updated data here but when i look to firebase database
//nothing updates !
}
});
For cases like this, I always recommend following the exact types in the documentation, to see what options are available. For example, a DocumentSnapshot object's data property is a Map<String, dynamic>. To when you call update() on that, you're just updating an in-memory representation of the document, and not actually updating the data in the database.
To update the document in the database, you need to call the DocumentReference.updateData method. And to get from the DocumentSnapshot to a DocumentReference, you call the DocumentSnapshot.reference property.
So something like:
document.reference.updateData(<String, dynamic>{
name: this.name
});
Unrelated to this, your code looks a bit non-idiomatic. I'd recommend using getDocuments instead of snapshots(), as the latter will likely result in an endless loop.
var snapshots = _firestore
.collection('profile')
.document(currentUserID)
.collection('posts')
.getDocuments();
await snapshots.forEach((document) async {
document.reference.updateData(<String, dynamic>{
name: this.name
});
})
The difference here is that getDocuments() reads the data once, and returns it, while snapshots() will start observing the documents, and pass them to us whenever there's a change (including when you update the name).
Update 2021:
Lot of things have changed in the API, for example, Firestore is replaced by FirebaseFirestore, doc is in, etc.
Update a document
var collection = FirebaseFirestore.instance.collection('collection');
collection
.doc('some_id') // <-- Doc ID where data should be updated.
.update({'key' : 'value'}) // <-- Updated data
.then((_) => print('Updated'))
.catchError((error) => print('Update failed: $error'));
Update nested value in a document:
var collection = FirebaseFirestore.instance.collection('collection');
collection
.doc('some_id') // <-- Doc ID where data should be updated.
.update({'key.foo.bar' : 'nested_value'}) // <-- Nested value
.then((_) => print('Updated'))
.catchError((error) => print('Update failed: $error'));
To update some fields of a document without overwriting the entire document, use the following language-specific update() methods:
final washingtonRef = FirebaseFirestore.instance.collection("cites").doc("DC");
washingtonRef.update({"capital": true}).then(
(value) => print("DocumentSnapshot successfully updated!"),
onError: (e) => print("Error updating document $e"));
Server Timestamp
You can set a field in your document to a server timestamp which tracks when the server receives the update.
final docRef = FirebaseFirestore.instance.collection("objects").doc("some-id");
final updates = <String, dynamic>{
"timestamp": FieldValue.serverTimestamp(),
};
docRef.update(updates).then(
(value) => print("DocumentSnapshot successfully updated!"),
onError: (e) => print("Error updating document $e"));
Update fields in nested objects
If your document contains nested objects, you can use "dot notation" to reference nested fields within the document when you call update():
// Assume the document contains:
// {
// name: "Frank",
// favorites: { food: "Pizza", color: "Blue", subject: "recess" }
// age: 12
// }
FirebaseFirestore.instance
.collection("users")
.doc("frank")
.update({"age": 13, "favorites.color": "Red"});
Update elements in an array
If your document contains an array field, you can use arrayUnion() and arrayRemove() to add and remove elements. arrayUnion() adds elements to an array but only elements not already present. arrayRemove() removes all instances of each given element.
final washingtonRef = FirebaseFirestore.instance.collection("cities").doc("DC");
// Atomically add a new region to the "regions" array field.
washingtonRef.update({
"regions": FieldValue.arrayUnion(["greater_virginia"]),
});
// Atomically remove a region from the "regions" array field.
washingtonRef.update({
"regions": FieldValue.arrayRemove(["east_coast"]),
});
Increment a numeric value
You can increment or decrement a numeric field value as shown in the following example. An increment operation increases or decreases the current value of a field by the given amount.
var washingtonRef = FirebaseFirestore.instance.collection('cities').doc('DC');
// Atomically increment the population of the city by 50.
washingtonRef.update(
{"population": FieldValue.increment(50)},
);
By using Cloud Functions, when a document from "users" collection is edited, the edited files should be updated in uploads collection wherever the user id is stored.
For the above requirement I am using the below function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const settings = {
timestampsInSnapshots: true
};
admin.initializeApp();
admin.firestore().settings(settings);
var db = admin.firestore();
exports.updateUser = functions.firestore.document('users/{userId}')
.onUpdate((change, context) => {
var userId = context.params.userId;
const newValue = change.after.data();
const name = newValue.display_name;
var uploadsRef = db.collection('uploads');
uploadsRef.where('user.id', '==', userId).get().then((snapshot) => {
snapshot.docs.forEach(doc => {
doc.set({"display_name" : name}); //Set the new data
});
}).then((err)=> {
console.log(err)
});
});
When this executes, I get the below error in the logs.
TypeError: doc.set is not a function
at snapshot.docs.forEach.doc (/user_code/index.js:31:21)
at Array.forEach (native)
at uploadsRef.where.get.then (/user_code/index.js:29:27)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
And also the below.
Unhandled rejection
How do I approach the problem? What is the best approach to deal with the snapshots document updates?
When you do a get() on a Query object, it will yield a
QuerySnapshot object. When you use its docs property, you're iterating an array of QuerySnapshotDocument objects that contain all the data from the matched documents. It looks like you're assuming that a QuerySnapshotDocument object has a set() method, but you can see from the linked API docs that it does not.
If you want to write back to a document identified in a QuerySnapshotDocument, use its ref property to get a DocumentReference object that does have a set() method.
doc.ref.set({"display_name" : name}); //Set the new data
Bear in mind that if you make this change, it will run, but may not update all the documents, because you're also ignoring the promise returned by the set() method. You'll need to collect all those promises into an array and use Promise.all() to generate a new promise to return from the function. This is necessary to help Cloud Functions know when all the asynchronous work is complete.
Im trying to retrieve some data from firestore within a cloud function, but get nothing back. The same query on the client-side gives me the correct results. It's probably something small but I don't see the issue. What am I doing wrong?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
exports.myFunction = functions.https.onCall((data, context) => {
const info = getInfo();
//do some stuff with the info
return info;
}
function getInfo() {
const query = db
.collection('info')
.where('table_nr', '==', 1)
.where('number', '<=', 25)
.orderBy('number', 'desc')
.limit(1);
const info = query.get().then(snapshot => {
snapshot.forEach(doc => {
return doc.data();
})
})
return info;
}
When I make a call to this function I get: "data: null"
let info = functions.httpsCallable('myFunction')
info().then(res => { console.log(res) })
I tried a lot of different options, like when I change the last part to:
const info = query.get().then(snapshot => {
snapshot.docs;
})
I get an array with 1 object. So I'm sure there is a document in the query with data. The console.log gives me:
{data: Array(1)}
data: Array(1)
0: {_ref: {…}, _fieldsProto: {…}, _serializer: {…}, _validator: {…},
_readTime: {…}, …}
length: 1
__proto__: Array(0)
__proto__: Object
And:
return query.get().then(querySnapshot => {
if (querySnapshot.empty) {
return { exists: false }
} else {
return { exists: true }
}
})
The console.log:
{data: {…}}
data:
exists: true
__proto__: Object
__proto__: Object
Mabye good to add that I created an (working) index for the query.
In both cases, you're returning a promise for an object that isn't what you really want to send to the client. When you write a callable, you need to return a promise that resolves to the exact JavaScript object that you want to send. You can't just return anything. What you'll have to do is convert that querySnapshot into plain old JavaScript objects that describe what you want the client to know. A querySnapshot object itself is not serializable - it is a complex object that describes many things about the query results.
First define this: What exactly do you want the client to receive? Define what the actual JavaScript object should look like. Now, convert the query results to look like that. At a minimum, you can send the entire set of documents as plain JS objects like this:
return query.get().then(querySnapshot => {
return querySnapshot.docs.map(doc => doc.data());
})
This will return an array to the client with raw document objects. But it's not clear to me that's what you want to send (since you didn't define your expectations). But it's a start.
So to give a clear example of using the .where in the Firebase Functions library for Firebase Cloud Functions
admin.firestore().collection("fruit")
.where("color", "==", "purple")
With ordering
admin.firestore().collection("fruit").orderBy("size")
.where("color", "==", "purple")
See demo doc if you want more details about how admin functions
Furthermore, the list of all query functions are found in the Query class can be used from the Firestore Functions library by using method chaining like my two examples in case you'd like things like "limit", "offset", etc.