Using onWrite Trigger for Realtime database in Firebase function - firebase

I designed a flutter application using dart and firebase backend. I want to get triggered when a user creates, delete, or updates anything in the database. I understood onCreate,onRemoved, and onUpdate methods.
I wrote a javascript code in the firebase cloud function. I need to send a notification when a trigger happens. I don't want to use 3 different triggers. I want to use onWrite trigger only.
This is my database.
There are four departments called comp, civil, elect, and mech. I want to get triggered when database changes happen in a single onWrite trigger!
My First question is, I want to differentiate whether it is created or delete or update from onWrite trigger. I know it will trigger for all 3 events. But how can I differentiate it?
This is my code for onCreate...
exports.noticeAddedComp = functions.database.ref('main/notices/comp/{id}').onCreate( async evt => {
var token = ['dxnfr3dq6_Y:APA91bHavtoP62l296qzVoBhzQJbMFA']
const payload = {
notification:{
title : 'Message from Cloud',
body : 'This is your body',
sound : 'default'
},
data:{
message : 'Thi is new Push',
click_action : 'FLUTTER_NOTIFICATION_CLICK',
}
};
await admin.messaging().sendToDevice(token,payload);
});
Above code is working. But it is for onCreate.
My second question is, Do I need to write four onWrite trigger codes for four departments?

You can use the change.before and change.after properties to determine whether the node was created, deleted, or updated in a onWrite handler:
exports.noticeAddedComp = functions.database.ref('main/notices/comp/{id}')
.onWrite( async (change, context) => {
if (!change.before.exists()) console.log("New node: "+change.after.key);
if (!change.after.exists()) console.log("Deleted node: "+change.before.key);
if (change.before.exists() && change.after.exists()) console.log("Updated node: "+change.after.key)
...
});
You can attach a Cloud Function to all departments at once, by including another parameter into its path:
exports.noticeAddedComp = functions.database.ref('main/notices/{dept}/{id}')
.onWrite( async (change, context) => {
console.log("Triggered for department: "+context.params.dept);
...
})

You can check if
evt.after.data().property;
is either null or doesn't exist, meaning it is deleted.
EDIT:
Scenario when you create something (document or field) :
Creation of the field would mean that the field in "before" doesn't exist at all, and in "after" it exists, but can be null.
Scenario when you update something
Field in the "before" is not the same as the one in "after".

Related

How to stop listening for snapshot updates in cloud firestore in Vuejs?

I'm using firestore in a messaging app I wrote with Vuejs.
The scenario is as follows: Active conversations are listed on the left side of the screen, clicking on it connects to the relevant collection and messages are listed on the right. So far everything is great.
Problem: When I click on each chat on the left, it connects to the corresponding collection. If more than one connection is made, the problem arises. While messaging with person A and receive a message from person B, the data in my right message box will change to B (Because I just clicked on that chat and subscribed to the collection).
Here is the function I run to list chats on page load:
mounted() {
const me = this.getRandomNumber()
firestore.collection('chat-groups').doc('messages').collection(`${me}`).onSnapshot(snapshot => {
this.chatGroups = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}))
})
},
Here is the function I run to list messages when I click on chat:
async getChatDetails(e) {
const me = this.getRandomNumber()
const pairId = e.id
this.activeChat.id = pairId
this.activeChat.userName = e.name
this.activeChat.profilePhoto = e.photo
firestore.collection('chat-groups').doc('messages').collection(`${me}`).doc(`${pairId}`).collection('messages').orderBy('createdAt', 'desc').limit(250).onSnapshot(snapshot => {
this.messages = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
})).reverse()
})
this.chatIsSelected = true
}
This is how I printed the messages stored in the this.messages variable to the page:
<ChatArea :messages="messages" />
I tried the following to close the connection but no results
firestore.collection('chat-groups').doc('messages').collection(`${me}`).onSnapshot()
firestore.collection().onSnapshot()
The solution I could think of was to unsubscribe from the previous subscription and start a new one when I clicked on a chat, but I was unable to do so.
As a result I want to be able to close the previous link both when I leave the page (beforeDestroy) and when I click on the other chat.
as you know that previous listener is not detached after you leave the previous chat and create a new listener for a new collection. so it still listening, i assume that you using options api, you can create a data "id" that will used for getting collection of message from props, and "listener" data for listening variable
attach listener data to snapshot in mounted function, after that create a watcher that watching data id changed or not, if changed, detach the listener variable and attach new listener with new id from props in watcher

How can I listen only to doc that changed in Firestore stream?

I have the following Firestore stream:
FirebaseFirestore.instance.collection("users").orderBy("timestamp", descending: true).snapshots().listen((value) async{
// here i noticed if doc was added or updated so the stream will take action to all docs
in that collection
});
For example if i write .set() to same collection like following:
.set({
name : 'Alex'
});
and I had another doc with the field name 'jack' then I used the following in the previous stream:
stream...{
if(value['name]=='Alex'){
print('ok');
}
if(value['name]=='jack'){
print('ok'); // ok here should print 'ok' if it first snapshot ,
but it will always print 'ok' whatever the new doc equal
my condition or not
}
}
How can I only listen to doc that newly added or updated or changed ?
As #Dharmaraj recommended, docChanges() method informs us of all the changes that have occurred in our database.
docChanges() method should be used on the snapshot parameter. Since that gives an array of document changes, we iterate over with a loop. If you make a console.log of the local variable of the loop, you will see that this returns each change (in the form of an object).
Example:
db.collection('recipes').onSnapshot(snapshot => {
// console.log(snapshot.docChanges());
snapshot.docChanges().forEach(change => {
console.log(change);
});
});
Do not forget that docChanges() should always be a function and never an array.
Additionally, you can add server timestamp. You can set a server timestamp in a field in your document to track when the server receives the update.
Each field receives the same server timestamp value when several timestamp fields are updated during a transaction.

Determine RTDB url in a trigger function

i m bulding a scalable chat app with RTDB and firestore
here is my raw structure of shards
SHARD1
Chats {
chat01: {
Info: {
// some info about this chatroom
},
Messages ...
}, ....
}
SHARD2...
now i have write triggers on all the info nodes of all the shards.
i want get the ID of the shard
How do i know what shard it actually ran on ?
[EDIT]
console.log(admin.app().name); // it prints "[DEFAULT]" in console
Puf and team please help
When a Realtime Database trigger is invoked, the second argument is an EventContext object that contains information about the database and node that was updated. That object contains a resource string, which has what you're looking for. According to the documentation for that string, it's name property will be formatted as:
projects/_/instances/<databaseInstance>/refs/<databasePath>
The databaseInstance string is what you're looking for. So, you can just split the string on "/" and take the 4th element of that array:
export const yourFunction = functions.database
.instance('yourShard')
.ref('yourNode')
.onCreate((snap, context) => {
const parts = context.resource.name.split('/')
const shard = parts[3]
console.log(shard)
})
If all you need is a reference to the location of the change, in order to perform some changes there, you can just use the ref property on the DataSnapshot that was delivered in the first argument, and build a path relative to there.

how do I get the dataID when using cloud firestore triggers function?

I have an Event Collection in the Firestore database like this:
I want to use cloud firestore triggers. when a user attends an event, the capacity of the event will be -1, and when this field is updated I want to automatically update another field ("rankPoint") +3
to implement this, I need to Trigger a function when a document is updated
from firestore documentation, it will be like this
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform desired operations ...
});
for my case, it should be 'events/{eventId}' right? but how do I get that eventID in the wildcard? does it come from client side? I mean in iOS/Android I will write the code to update like
db.collection("eventss").document("someEventIDHere").setData(data)
is it from the client?
Your function will only be delivered the document that matched your function's pattern (users/{userId}) that was changed. Other documents are not available until you query for them. So, if you want a document from you events collection, you will have to write some code to access it, then decide what to do from there.
It sounds like you're expecting there to be an eventId wildcard, but there is not. There is just the userId wildcard that you defined in your function. Other values will need to be derived from the data you have available in your user document.

Firebase React Native fetch data

I am trying to do an app on react-native with a feed. On my main screen, I go fetch the data :
fetchData() {
firebase.database().ref(`/posts/${group}`).on('value', async snapshot => {...}
}
when I want for example to like a comment of the post, I first push the data into firebase with different queries, for example :
export const likeComment = (...) => {
firebase.database().ref(`/posts/${group}/${post}`).update
({
updatedAt: firebase.database.ServerValue.TIMESTAMP
});
firebase.database().ref(`/posts/${group}/${post}/lastComments`).set(...);
But I realized that my first function fetchData was called 3 times.
then I grouped my queries like :
let updates = {}
updates[`/posts/${group}/${post}/lastComments`] = {...};
updates[`/posts/${group}/${post}`] = { ... };
firebase.database().ref().update(updates);
Then, fetchData was called still 2 times.
I was wondering if that was the best way to do it, and why does my function fetchData was still called twice.
Thanks for your help
It's hard to tell from the limited code, but my guess is that fetchData is not actually being called more than once, but instead the snapshot is firing when you make updates to your Firebase database.
The part of your code where you say .on('value', async snapshot => you're setting up a listener to send you updates any time that value changes.
So my guess is that your three invocations are:
The first time you actually call fetchData and set up the
listener
Your .on( snapshot listener firing when you call
update
Your .on( snapshot listener firing again when you
call set
This push-based database workflow is one of the main benefits of Firebase. If you only want to get the data once and not get live updates, you can call .once( instead of .on(
https://firebase.google.com/docs/database/web/read-and-write

Resources