cloud functions for firebase onwrite not triggering any executions - javascript

exports.editData = functions.database.ref('/AllData/hello/A').onWrite((change, context) => {
const after = change.after;
if (after.exists()) {
const data = after.val();
var value = data;
// set of data to multiply by turns ratio
var actualEIn = (value.ein)*200;
console.log('Data Edited');
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Edit: made some edits to the code as suggested! However, when I deploy it there are literally no logs.

Change this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((snapshot) => {
const data = snapshot.val();
if (data.exists()) {
into this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change,context) => {
const data = change.after.val();
if (data.exists()) {
more info here:
https://firebase.google.com/docs/functions/beta-v1-diff#realtime-database

exports.editData = functions.database.ref('/AllData/hello/A/{id}').onWrite((change, context) => {
const afterData = change.after;
if (afterData.exists()) {
console.log('hey');
const data = afterData.val();
// set of data to multiply by turns ratio
var actualEIn = (data.ein)*200;
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Hi guys thank you for all your help! :) I managed to solve this by adding a /{id} at the back!

You've got two things wrong here.
First, newer versions of the firebase-functions SDK since version 1.0 deliver a Change object to onWrite handlers instead of a snapshot, as it appears you are expecting. The Change object has properties for before and after with DataSnapshot objects of the contents of the database before and after the change that triggered the function. Please read the documentation for database triggers to get all the information.
Second, exists() is a method on DataSnapshot, but you're using it on the raw JavaScript object value of the contents of the database the location of change. JavaScript objects coming from val() will not have any methods to call.
You should probably update your code to:
Use the latest version of the firebase-functions module
Alter your function to accept the Change object instead of a snapshot
Use the exists() method on a snapshot in the change, rather than a raw JavaScript object.
Starter code:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change) => {
const after = change.after; // the DataSnapshot of the data after it was changed
if (after.exists()) {
const data = after.val() // the raw JavaScript value of the location
// use data here
}
})

Related

Firebase Firestore - Async/Await Not Waiting To Get Data Before Moving On?

I'm new to the "async/await" aspect of JS and I'm trying to learn how it works.
The error I'm getting is Line 10 of the following code. I have created a firestore database and am trying to listen for and get a certain document from the Collection 'rooms'. I am trying to get the data from the doc 'joiner' and use that data to update the innerHTML of other elements.
// References and Variables
const db = firebase.firestore();
const roomRef = await db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(snapshot => {
snapshot.docChanges().forEach(async change => {
if (roomId != null){
if (role == "creator"){
const usersInfo = await roomRef.doc(roomId).collection('userInfo');
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = await doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
})
})
})
However, I am getting the error:
Uncaught (in promise) TypeError: Cannot read property 'joinerName' of undefined
Similarly if I change the lines 10-12 to:
remoteUser = await doc.data();
remoteNameDOM.innerHTML = `${remoteUser.joinerName} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser.joinerName}`;
I get the same error.
My current understanding is that await will wait for the line/function to finish before moving forward, and so remoteUser shouldn't be null before trying to call it. I will mention that sometimes the code works fine, and the DOM elements are updated and there are no console errors.
My questions: Am I thinking about async/await calls incorrectly? Is this not how I should be getting documents from Firestore? And most importantly, why does it seem to work only sometimes?
Edit: Here are screenshots of the Firestore database as requested by #Dharmaraj. I appreciate the advice.
You are mixing the use of async/await and then(), which is not recommended. I propose below a solution based on Promise.all() which helps understanding the different arrays that are involved in the code. You can adapt it with async/await and a for-of loop as #Dharmaraj proposed.
roomRef.onSnapshot((snapshot) => {
// snapshot.docChanges() Returns an array of the documents changes since the last snapshot.
// you may check the type of the change. I guess you maybe don’t want to treat deletions
const promises = [];
snapshot.docChanges().forEach(docChange => {
// No need to use a roomId, you get the doc via docChange.doc
// see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange
if (role == "creator") { // It is not clear from where you get the value of role...
const joinerRef = docChange.doc.collection('userInfo').doc('joiner');
promises.push(joinerRef.get());
}
});
Promise.all(promises)
.then(docSnapshotArray => {
// docSnapshotArray is an Array of all the docSnapshots
// corresponding to all the joiner docs corresponding to all
// the rooms that changed when the listener was triggered
docSnapshotArray.forEach(docSnapshot => {
remoteUser = docSnapshot.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
});
});
However, what is not clear to me is how you differentiate the different elements of the "first" snapshot (i.e. roomRef.onSnapshot((snapshot) => {...}))). If several rooms change, the snapshot.docChanges() Array will contain several changes and, at the end, you will overwrite the remoteNameDOM and chatNameDOM elements in the last loop.
Or you know upfront that this "first" snapshot will ALWAYS contain a single doc (because of the architecture of your app) and then you could simplify the code by just treating the first and unique element as follows:
roomRef.onSnapshot((snapshot) => {
const roomDoc = snapshot.docChanges()[0];
// ...
});
There are few mistakes in this:
db.collection() does not return a promise and hence await is not necessary there
forEach ignores promises so you can't actually use await inside of forEach. for-of is preferred in that case.
Please try the following code:
const db = firebase.firestore();
const roomRef = db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(async (snapshot) => {
for (const change of snapshot.docChanges()) {
if (roomId != null){
if (role == "creator"){
const usersInfo = roomRef.doc(roomId).collection('userInfo').doc("joiner");
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
}
})

real time update from firebase

I have a function that's doing calls for firebase database and return those data. I'm trying to implement a listener to this function so when the database updates, the content in my web site also updates without refresh.
My function is as follows
export const loadBookings = async () => {
const providersSnapshot = await firebase.database().ref('products').once('value');
const providers = providersSnapshot && providersSnapshot.val();
if (!providers) {
return undefined;
}
return providers;
};
After going through some documentation i have tried changing itto something like this
const providersSnapshot = await firebase.database().ref('products').once('value');
let providers = "";
providersSnapshot.on('value', function(snapshot) {
providers = snapshot.val();
});
But the code doesn't work like that. How can i listen in real time for my firebase call?
Use on('value') instead of once('value'). once() just queries a single time (as its name suggests). on() adds a listener that will get invoked repeatedly with changes as they occur.
I suggest reading over the documentation to find an example of using on(). It shows:
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', function(snapshot) {
updateStarCount(postElement, snapshot.val());
});

Dynamically get data from firebase realtime database during runtime in javascript

I have the following problem: I want to get data from a specific node from firebase during runtime. It should display "stats" of a player that was selected before. Now I could use on() to get all the data in the beginning, but I want to save data transfers by only downloading the data of on player if I need to, so I tried to use once like this:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
firebaseRef.once('value').then(function(snap) {
snap.child('scores').child('thierschi').forEach(function(child) {
selectedPlayerScores.push([child.key, child.val()]);
});
});
return selectedPlayerScores;
}
The problem is that it retruns the array before the data was loaded into it. Also I checked the docs and didn't find a better solution.
Thanks in advance!
This is because the getScoresOfPlayer function returns selectedPlayerScores before the promise returned by the once() method resolves.
You should include the return within the then(), as follows:
var firebaseRef = firebase.database().ref();
function getScoresOfPlayer(player) {
console.log(player);
var selectedPlayerScores = [];
return firebaseRef.once('value') //return here as well
.then(function(snap) {
snap.child('scores').child(player).forEach(function(child) { //I guess it should be child(player) and not child('thierschi') here
selectedPlayerScores.push([child.key, child.val()]);
});
return selectedPlayerScores;
});
}
which means that you have to call your function as follows, since it is going to be asynchronous and to return a promise:
getScoresOfPlayer('xyz')
.then(function(selectedPlayerScores) {
....
})

Firebase functions - Counting children and updating an entry

I have been trying to use Firebase Functions to write a simple method, but I am unfamiliar with JS.
Below is the structure of my Realtime Database
-spots
---is_hidden: false
---likes
------like_id_1: true
---dislikes
------dislike_id_1: true
I am trying to write a simple method that does the following: Whenever an entry is added to dislikes, count the likes and the dislikes.
If the number of dislikes is larger than the number of ( likes + 5 ),
change the value of is_hidden to true
This is my attempt to solving the problem
exports.checkHiddenStatus = functions.database.ref('/spots/{spotid}').onWrite(
(change, context) => {
const collectionRef = change.after.ref;
const isHiddenRef = collectionRef.child('is_hidden');
const likesRef = collectionRef.child('likes');
const dislikesRef = collectionRef.child('dislikes');
if(isHiddenRef.before.val()) return;
let likeCount = likesRef.numChildren();
let dislikeCount = dislikesRef.numChildren();
let isHidden = false;
if( dislikeCount >= (likeCount + 5))
isHidden = true;
if(!isHidden) return;
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return isHiddenRef.transaction((current) => {
return isHidden;
}).then(() => {
return console.log('Counter updated.');
});
});
Sadly, because I have no experience with JS I keep getting stuck with error messages I don't understand. The most recent being
TypeError: Cannot read property 'val' of undefined
at exports.checkHiddenStatus.functions.database.ref.onWrite (/user_code/index.js:28:28)
Can somebody please help me write this function? Thank you!
It looks like you're trying to treat a database Reference object like a Change object. Change has before and after properties, but a reference does not.
If you have a database reference object, and you want the value of the database at that location, you need to query it with its once() method.
Read more about reading and writing data using the Admin SDK.

How to get variables from other database nodes with Cloud Function .onWrite (Firebase)

I am trying to set some variables to send to pass into a function if a Firebase node is set to true. I am trying to use the .parent and .val() function to set a customer_id, based on the documentation here: https://firebase.google.com/docs/functions/database-events
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
let snap = event.data;
let customer_id = snap.ref.parent.child('customer_id').val();
console.log(customer_id);
// pass customer_id into function
}
I was expecting snap.ref.parent to reference /sources and .child('customer_id').val() to access the value from the customer_id key.
However, when I try to run this function I get the following error:
TypeError: snap.ref.parent.child(...).val is not a function
at exports.linkCardToSquareAccount.functions.database.ref.onWrite.event (/user_code/index.js:79:56)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
How can I reference a node outside the scope of the original onWrite location?
You can't just call .val() on a database reference and expect to get the data at that location. You need to add a value listener in order to get new data.
Fortunately, this is fully supported inside of Cloud Functions:
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
const customerIdRef = event.data.adminRef.parent.child('customer_id')
// attach a 'once' value listener to get the data at this location only once
// this returns a promise, so we know the function won't terminate before we have retrieved the customer_id
return customerIdRef.once('value').then(snap => {
const customer_id = snap.val();
console.log(customer_id);
// use customer_id here
});
}
});
You can learn more here.

Categories