I am trying to create a function for my database using Cloud Functions for Firebase. The purpose of the function is to listen to write events on the attend table and based on the object written to identify the event and increment the usersAttending on the event object.
This is my function so far.
//listens to write on attendObjects (when a user is attending an event), and increments attending users for event
exports.listenAttendingEvents = functions.database.ref('/attend/{pushId}').onWrite(event => {
//get attendObj -> parsed JSON by javascript interpreter
const attentObj = event.data.val();
const attendId = attentObj['attendId'];
const pathToAttendees = '/attends' + '/' + attendId;
// Attach an asynchronous callback to read the data at our posts reference
admin.database().ref(pathToAttendees).on("value", function(snapshot) {
console.log(snapshot.val());
const obj = snapshot.val();
var nrAttending = obj['attending'];
nrAttending = Number(snapshot.val());
return admin.database().ref(pathToAttendees + '/attending').transaction(function (nrAttending) {
return (nrAttending || 0) + 1;
});
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
return errorObject
});
The problems as it seems is that the event object doesn't get retrieved. The function seems to finish before that with the status ok
The problem was that I was not having a promise for my top-level function. This caused Google Cloud Functions to kill it before the operation was complete.
Adding a promise solved my problem
admin.database().ref(pathToAttendees).once("value").then( function(snapshot) {
Related
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());
});
I am currently trying to get an indexedDB database running. However, I am struggling with some issues regarding indexedDB's put method. Although the keypath is defined and the JSONObject that is handed over contains a value which is named in the same way as the defined keypath, the put method causes the following error:
Uncaught DOMException: Failed to execute 'put' on 'IDBObjectStore': Evaluating the object store's key path did not yield a value.
In order to make sure that the JSONObject really contains the value that shall be used as the key, I am logging the object. Thats what it looks like:
{"key":102019,"month":10,"year":2019,"spendings":[{"type":{"name":"Technology","importance":70,"iconURL":"./Resources/Technology.png"},"cost":"1500","name":"Macbook pro","timestamp":1571696285911}],"budget":0}
The code that is being used to store the data is the following:
function callbackSaveSpendingMonth(database, spendingMonth) {
let userName = defaultUserName;
let transaction = database.transaction(userName, "readwrite");
let objectStore = transaction.objectStore(userName, { keyPath: 'key' });
let JSONspendingMonth = JSON.stringify(spendingMonth);
console.log(JSONspendingMonth);
let request = objectStore.put(JSONspendingMonth);
request.onsuccess = function (event) {
console.log("The month " + spendingMonth.getMonth() + "/" + spendingMonth.getYear() + " has been saved successfully!");
}
transaction.oncomplete = function (event) {
console.log("A connection to indexedDB has successfully been established!");
}
}
I am using the on() method to retrieve a data snapshot in our database, but I need to be able to store this snapshot value so that I can use it to retrieve another separate snapshot.
Here is what our database looks like:
Firebase Real-Time Database
There is a node for users and a separate node for devices. Each user has a child "devices" which is a list of devices associated with that user. The user that I have expanded only has one device.
What I am trying to do is store this deviceID, and then do a separate query to find that device in the "Devices" node. Here is what my code looks like:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData);
And then the callback function looks like this:
function getData(data)
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
}
which is just grabbing the first device in the users device list and printing it to the console. I am trying to figure out how to
return this value so that I can use it when getting the data from the Devices tree. Which, I'm guessing,
would look something like this:
let deviceRef = database.ref("devices/").child(retrievedValue);
deviceRef.on("value", getData2);
Where retrievedValue is the deviceID that I got from the first query.
Is it possible to do this in javascript, or is there a better way? I know similar questions have already been asked, but I've found all the examples I've seen online to be really confusing and not very helpful for me. Any help at all would be super appreciated because I am kind of stuck on this. Thanks!
You have to learn about promises and asynchronous programming. Here are two ways to do what you want:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.once("value").then((data) {
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
return deviceRef.once("value");
}).then((value) {
console.log("value is " + value);
})
or with async/await:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
let data = await ref.once("value")
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
let value = await deviceRef.once("value");
console.log("value is " + value);
I'm more confident about the second one as I'm typing these without testing.
These links would be helpful to start learning this stuff:
https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
https://firebase.google.com/docs/functions/terminate-functions
Edit: I fixed the code above by replacing on with once. However now this is not listening to changes in the db anymore. To correct your code to listen to user's device changes:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData);
function getData(data) // may need to place this before the code above
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
// no need to listen to this, as a change in one device would fire
// for every user. you probably don't want that.
deviceRef.once("value", (data) {
console.log(data);
});
}
In order to achieve that, you have to modify your callback as following:
function getData(data, callback)
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
callback(currentDevice)
}
Then we you call your callback from within the code, do it like this:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData((this_is_the_value_from_inside_callback) => {
console.log(`your value: ${this_is_the_value_from_inside_callback}`)
});
You can also try to run this little snippet (I used PlayCode), to see it more friendly testing environment
somefunction = (data, callback) => {
console.log(`data: ${data}`)
data += 100
console.log(`data: ${data}`)
callback(data)
}
somefunction(100, (dataReturned) => {
console.log(`data returned: ${dataReturned}`)
})
Working on the IndexedDB API, I'm creating many objectStores that belong to the same database, in one transaction, when the user loads a webpage.
I order to do so, I created an object which contains many objectStores to be created, each one has it's name, data and index.
Then a function runs the object and effectively creates Database, objectStores and indexes for each one.
However of all OS's created, just the last member of the object gets populated. Say of 5 objects to be created and populated, 5 are created but only the last one is populated.
Clearly is a problem of overwriting or some issue related to the JS stack or asynchronicity.
I appreciate any help to make the code populate all OS not the last one.
My browser is Chrome 56, I fetch data from an API whose response is OK, and I'm coding on vanillajs. I appreciate your help in vanillajs, there is no way to use any library or framework different from what the modern Web Platform offers.
Here is the code:
On the HTML side, this is an example of the object:
var datastores = [{osName:'items', osEndpoint: '/api/data/os/1/1', osIndex:'value'}, {osName:'categories', osEndpoint: '/api/data/os/2/1', osIndex: 'idc'}];
On javascript:
var request = indexedDB.open(DB_NAME, DB_VERSION); // open database.
request.onerror = function (e) { // error callback
console.error("error: " + e.target.errorCode);
};
request.onupgradeneeded = function (e) { // the onupgradeneeded event which creates all schema, dataabase, objectstores and populates OS.
var db = this.result;
for (var i in datastores) { // loop the objectStore object.
var objectStore = db.createObjectStore(datastores[i].osName, {keyPath: "id"});
TB_NAME = datastores[i].osName; // instantiate each objectStore name.
objectStore.createIndex(datastores[i].osIndex, datastores[i].osIndex, { unique: false }); // create each index.
objectStore.transaction.oncomplete = function(e) { // oncomplete event, after creating OS...
fetchGet(datastores[i].osEndpoint, popTable); // runs a function to fetch from a designated endpoint and calls a function.
};
}
}
Now the functions: to fetch data and to populate data:
function fetchGet(url, function) { // fetch from API.
fetch(url, {
method: 'GET'
}).then(function(response) {
return response.json();
}).then(function(json) {
popTable (json);
}).catch(function(err) {
console.log('error!', err);
});
}
function popTable(json) {
var m = 0;
var tx = db.transaction(TB_NAME, "readwrite");
tx.oncomplete = function(e) {
console.log("Completed Transaction " + TB_NAME);
};
tx.onerror = function(e) {
console.error("error: " + e.target.errorCode);
};
var txObjectStore = tx.objectStore(TB_NAME);
for (m in json) {
var request = txObjectStore.add(json[m]);
request.onsuccess = function (e) {
console.log('adding... ' );
};
}
}
The for (var i in datastores) loop runs synchronously, updating the global TB_NAME variable every time. When the loop finishes, TB_NAME will be holding the name of the last object store.
By the time the asynchronous popTable calls run, TB_NAME will forever be holding the name of the last store, so that's the only one that will update. Try adding logging to popTable to see this.
You'll need to pass the current value of the store name along somehow (e.g. as an argument to fetchGet). Also note that although you pass popTable as a parameter when calling fetchGet you're not actually accepting it as an argument.
...
Specific changes:
Change how you call fetchGet to include the store name:
fetchGet(datastores[i].osEndpoint, popTable, datastores[i].osName);
Change the fetchGet function to accept the args:
function fetchGet(url, func, name) {
And then instead of calling popTable directly, do:
func(json, name);
And then change the definition of popTable to be:
function popTable(json, name) {
... and use name in the transaction.
I am developing my app, and one of the features will be messaging within the application. What I did, is I've developed 'send message' window, where user can send message to other user. The logic behind it is as following:
1. User A sends message to User B.
2. Firebase creates following nodes in 'Messaging':
"Messaging"->"User A"->"User B"->"Date & Time"->"UserA: Message"
"Messaging"->"User B"->"User A"->"Date & Time"->"UserA: Message"
Here is the code that I am using for sending messages:
sendMsg: function(receiver, content) {
var user = Auth.getUser();
var sender = user.facebook.id;
var receiverId = receiver;
var receiverRef = $firebase(XXX.firebase.child("Messaging").child(receiverId).child(sender).child(Date()));
var senderRef = $firebase(XXX.firebase.child("Messaging").child(sender).child(receiverId).child(Date()));
receiverRef.$set(sender,content);
senderRef.$set(sender,content);
},
(picture 1 in imgur album)
At the moment, I am trying to read the messages from the database, and sort them in according to date. What I've accomplished so far, is that I have stored the content of "Messaging/UserA/" in form of an Object. The object could be seen in the picture I've attached (picture 2).
http://imgur.com/a/3zQ0o
Code for data receiving:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
My question is: how can I read the object's messages? I would like to sort the according to the date, get the message and get the Id of user who has sent the message.
Thank you so much!
You seem to be falling for the asynchronous loading trap when you're reading the messages:
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
Messages.on("value", function (snapshot) {
var messagesObj = snapshot.val();
return messagesObj;
}, function (errorObject) {
console.log("Error code: " + errorObject.code);
});
}
That return statement that you have in the Messages.on("value" callback doesn't return that value to anyone.
It's often a bit easier to see what is going on, if we split the callback off into a separate function:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var messagesObj = snapshot.val();
return messagesObj;
},
getMsgs: function () {
var user = Auth.getUser();
var userId = user.facebook.id;
var messagesPath = new Firebase("https://xxx.firebaseio.com/Messaging/");
var Messages = messagesPath.child(userId);
console.log('Before adding on-value listener');
Messages.on("value", onMessagesChanged);
console.log('After adding on-value listener');
}
If you run the snippet like this, you will see that the console logs:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
This is probably not what you expected and is caused by the fact that Firebase has to retrieve the messages from its servers, which could potentially take a long time. Instead of making the user wait, the browser continues executing the code and calls your so-called callback function whenever the data is available.
In the case of Firebase your function may actually be called many times, whenever a users changes or adds a message. So the output more likely will be:
Before adding on-value listener
After adding on-value listener
Inside on-value listener
Inside on-value listener
Inside on-value listener
...
Because the callback function is triggered asynchronously, you cannot return a value to the original function from it. The simplest way to work around this problem is to perform the update of your screens inside the callback. So say you want to log the messages, you'd do:
onMessagesChanged(snapshot) {
// when we get here, either the messages have initially loaded
// OR there has been a change in the messages
console.log('Inside on-value listener');
var i = 0;
snapshot.forEach(function(messageSnapshot) {
console.log((i++)+': '+messageSnapshot.val());
});
},
Note that this problem is the same no matter what API you use to access Firebase. But the different libraries handle it in different ways. For example: AngularFire shields you from a lot of these complexities, by notifying AngularJS of the data changes for you when it gets back.
Also see: Asynchronous access to an array in Firebase