How to prevent 'value' event on the client that issued set? - javascript

A Firebase client calling set() will cause all connected clients to have value triggered - including - the original client that issued the set().
In my case (and I think in most cases), there is no reason for the client that issued the set() to respond to the value event produced by its own call. Obviously its model is correct and there's no need to change it (which may be an expensive operation).
Is there any way for the client to not-receive/prevent/ignore the value event triggered by its own set() call ? I considered using off/on around set() but that can make the client miss value events that came at the same time but were not triggered by it.
Am I missing something obvious ?

Most applications treat the Firebase data itself as their model. So when there's an update, they call ref.set() (or another mutator function) and then the update flows back into their app through an on() event. React/Flux aficionados know this as a unidirectional data-flow, other might know it as Command Query Responsibility Segregation.
But there indeed cases where the model has already been updated and thus you want to ignore the event from Firebase if you're the one who triggered it.
There is no API for not receiving theses self-triggered events. Instead you'll have to "remember" the data that you sent to Firebase and filter it out in your on() handler.
The Android drawing sample from Firebase keeps a list of segments that it sends to Firebase and then ignores those segments in its onChildAdded handler. It uses push ids to identify the line segments and those are generated client-side, so it can use those to track identify the segments.
A JavaScript sample of this:
var pendingChildIds = []; // Push ids of nodes we've sent to the server, but haven't received in `on()` yet
// this code is in your UI event handler, or whatever triggers the needs to update your Firebase data
var newChild = ref.push();
pendingChildIds.push(newChild.key());
newChild.set(
{ property1: 'value1', property2: 3.14 },
function(error) {
// the write operation has completed, remove the child id from the list of pending writes
pendingChildIds.splice(pendingChildIds.indexOf(newChild.key());
}
);
// this is the event handler, using child_added in this case
ref.on('child_added', function(snapshot) {
if (!pendingChildIds.contains(snapshot.key())) {
// this is a child that we DIDN'T generate
}
});

I ended up adding a client ID to the model, something like:
var clientId=(Math.random()*10000000000000000).toFixed(0);
function set(data) {
ref.set(JSON.stringify({ clientId: clientId, data: data }));
}
ref.on('value', function(snapshot) {
var json=JSON.parse(snapshot.val());
if (!json || json.clientId===clientId) return;
var data=json.data;
// update model with data
});

Related

How to only read changes in firebase realtime database

I have a database collection with readings, each new reading needs to be checked if it's out of the ordinary, if it is, there needs to be an alert sent.
So i'm using db.ref('collection').on('child_added', (child => { check(child); });
The problem with the .on function is that when the listener is added, all previous data is also read.
So how do i read a collection that only reads the changes in the database, also when the listener is first added? Or if that doesn't work, how do I differentiate the already added data with the new data?
The Firebase database synchronizes the state of whatever query or reference you attach your listener to. There is no option to only get new nodes built into the API.
If you want only new nodes, you will have to:
Ensure each node has an associated timestamp or order. If you're using Firebase's built-in push() keys, those might already serve that function.
Know what "new" means to the client, for example by either keeping the last timestamp or push key that it saw.
And then use a query to only request nodes after the stores timestamp/key.
So for example, if you only want to read nodes that are created after the moment you attach the listener, you could do something like this:
let now = db.ref('collection').push().key; // determine current key
db.ref('collection').orderByKey().startAt(now).on('child_added', ...)

Firebase trigger on all collection items while only one has been updated

Every time I do an update with the same object(s3) with same values and properties, Firebase trigger the event 'child_added' even if there's nothing to add or update.
I made some test by modifying on the firebase console some values in subcollection of the main object and noticed that it returns a snapshot with the first element correct and then all the other elements of the collections as 'ADDED' elements. This is not true because the collections didn't change except the one on which I performed an action.
I just need that when I send the same identical object that is stored on the db, firebase will recognize smartly that no action is requested and no trigger need to be activated.
var studentiRef = ref.child('studenti/' + s3.matricola);
studentiRef.update(JSON.parse(JSON.stringify(s3)));
studentiRef.on("child_changed", function(userSnapshot) {
var tasseRef = userSnapshot.ref.child('tasse');
tasseRef.on('child_added', function(itemSnapshot, prevKey){
console.log('ADDED ON');
console.log(itemSnapshot.key)
})
});
studentiRef.on("child_changed", function(userSnapshot) {
userSnapshot.ref.child('tasse').on('child_removed', function(itemSnapshot, prevKey){
console.log('REMOVED ON');
console.log(itemSnapshot.key)
})
});
studentiRef.on("child_changed", function(userSnapshot) {
userSnapshot.ref.child('tasse').on('child_changed', function(itemSnapshot, prevKey){
console.log('CHANGED ON');
console.log(itemSnapshot.key)
})
});
UPDATE:
Before posting update I made some experiments with no successful results.
Here the pics of the console, the database and the code.
Going nuts on this.
Here three screenshot: 1 firebase data 2 snippet 3 console log
UPDATE II:
scenario
behaviours on modifying value in firebase
SOLVED:
By getting inspired from the github firebase examples, I found out a common mistake in using firebase: i was not flatting the data.
To continue using my data structure (a root object within a list of objects), the solution was to trigger an update of every single object (pseudocode: ref.update(root/childobject) n-times instead of ref.update(root).
If someone else ran into this problem, I will explain better.
Always, FLAT YOUR DATA! (using firebase)
Most likely these events come directly from the client SDK, which doesn't detect if there was an actual change. The database server does perform such a check, and will only send out changes to other clients if there was an actual change.
Update:
The Firebase client + server behave in the following way when you're calling telling it to update a node to its current value.
The client fires the local event(s) to reflect the update. So child_changed will fire.
The client send the update to the server. This is needed since the client and server may be (slightly) out of sync, and the server is the single-source-of-truth.
The server compares the update with the current value of the node. If it is the same, the process stops here.
If the updated value is different from the current value and passes validation/permission checks, the data is committed to disk and broadcast to any active listeners.
If the updates value is different, but rejected by the validation/permission checks, the servers sends a rejection message to the original client, which then fires another child_changed event to revert the local change.

Firebase database on push get id

I am trying to imitate an insertion trigger on Firebase using the onWrite method. The insertion is done via POST requests since I am testing it (easiest way I found to check database triggers). The trigger includes writing the Firebase generated ID inside the inserted data as a new property.
My cloud function is this:
exports.onNewSeries = functions.database.ref('/series').onWrite(event => {
"use strict";
console.log(event.data.key);
console.log(event.data.current.key);
console.log(event.data.current);
});
Both first logs contain the same key (series), which actually is the key of the parent node where the new data is appended, instead of the new data key (in the quirky form of -adfaa123sdfasdf). The last log prints a Firebase structure containing the new data as well as the generated key in a _data property, however it is not accessible.
While this can be done manually after a request, I have not seen it automated in a database trigger way.
To get the generated key, make the function trigger on a specific child:
exports.onNewSeries = functions.database.ref('/series/{id}').onWrite(event => {
console.log(event.params.id);
});
Also see the Firebase documentation on handling database events.

Retrieving Objects from Firebase using Value() Method

I cant figure out how to get "value" to read the data in my FB object the same way that the "child_added" does.
The following code:
postNotes.on("value", function(snapshot){
console.log("VALUE FUNCTION REACHED")
var note = snapshot.val();
console.log(note.noteObject);
});
postNotes.on("child_added", function(snapshot){
console.log("CHILDADDED FUNCTION REACHED");
var note = snapshot.val();
console.log(note.noteObject);
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
...displays this in the console:
And here is the data in the FB console:
I don't use any libraries. I would simply like the data from FB to display when my page loads, and continue to update each time new data is pushed to the FBDB.
Am I missing the point of the "value" parameter? What can I do better?
Please forego the answers that involve things such as "read the docs", "use jquery", or otherwise useless answers some StackO users seem to be so fond of.
I've been digging around this problem of mine for a few hours and based on what else I see in StackO a few others have had a similar issue. Thank you so much for taking a look at my question and lending any insight you may have.
You use the same postsRef to listen to both value and child_added events. This is not a common use patterns.
The value event fires when the value of the node changes. So whenever the parent node of -J....NI3 changes, the entire parent node is passed into your callback as the snapshot. If you check the data in the Firebase dashboard, you'll see that this parent node does not have a noteObject child.
Compare that to the child_added event, which fires when a child node is added to the node on which you listen. The callback to child_added in that case gets the new child node passed as its snapshot. And the new child node indeed has a noteObject child.
You'll normally listen to value events on single, lowest-level nodes. When you have a collection of nodes (like you do here), you'll usually use child_added (and its _changed, _deleted and _moved brethren). But just in case you really want to use on('value' with a collection:
postNotes.on('value', function(notesSnapshot) {
notesSnapshot.forEach(function(noteSnapshot) {
console.log(noteSnapshot.val());
});
});
Just be aware that such an approach is less efficient than listening to the various child_ events, especially as you dynamically add or change children in the collection.

Updating the client view in Meteor js after a database insertion

First off, some background
My client has a kind of a "split-view", meaning- a side-panel displaying a list of objects and a main view displaying the selected object's details. Every time the user clicks on an Object in the list, a Backbone's route is called to navigate to the id which updates a "selected" property on the Session, what causes the main view to update- pretty standard stuff.
The problem
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
My goal is that every time an Object is created, the list and the main view will be instantly updated to reflect the newly added Object. To achieve this I created a Meteor.method, create(), that uses Collection.insert and returns the id so I can use it with my Route. The method is shared across the client and server and is being called from within a template's event handler.
My first try was to store the returned id in a variable in the event handler and update the Route in the next line; For some reason, that didn't work because the method returned an undefined value. So I tried a different approach, instead of returning the id, I used it within the method to update the Route directly (if Meteor.isClient of course). That didn't work either because the id returned by Collection.insert in the client's version of the method was different from the one in the server's version.
First approach
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = Meteor.call('create');
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});
Second approach
Meteor.methods({
create: function () {
var ObjectId = Objects.insert({name:'test'});
if(Meteor.isClient){
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
}
});
If anyone knows what's going on and can give me some directions that would be great.
Any different approaches to the problem or suggestions would be much appreciated as well.
Thanks
Update
So I tried #Pent's suggestion and I got the same result as with my second approach. For some odd reason Meteor decides to ignore my id (created with Random.id()) and inserts the object with a different one.
So I tried another approach, I used just a simple string value instead of Random.id() and voila - it worked. Riddle me that.
Answer updated:
This will be both a client and server method:
Meteor.methods({
create: function () {
var id = Random.id();
Objects.insert({_id: id, name:'test'});
if(this.isSimulation) {
appRouter.navigate("object/id/" + id, {trigger:true});
}
}
});
You can view a similar pattern from Meteor's party example: https://github.com/meteor/meteor/blob/b28c81724101f84547c6c6b9c203353f2e05fbb7/examples/parties/model.js#L56
Your problem is coused by the fact that remote methods, i.e. those which will be called on the server, don't simply return any value. Instead, they accept a callback that will be used to process the returned value (see docs). So in your first example you should probably do something like this:
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
Meteor.call('create', function (error, result) {
if (!error)
appRouter.navigate("object/id/" + result, {trigger:true});
});
}
});
You also said:
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
I think that in this case you should definitely wait for server response. Note, that there is no chance you get the correct object id unless this is given to you by the server.
One possible way to get around this issue is to create a local (client-side) collection:
// only on client
var temporary = new Meteor.Collection(null); // null name
in which you could store your "temporary" newly created objects, and then save them to the "real" collection after the user clicks the save button. You could implement your router to respond to urls like object/new/* to get access to these objects before they're saved to your database.
The correct answer for this question is defining a client side method that's responsible for creating the unique id (preferably using Random.id() ) and calling the Meteor.methods' create(). That way, you can have the id available immediately without waiting for the server to generate one. The trick here is to generate the id outside of the Meteor.method so that the id generation happens only once for both the stub and the actual server method.
create = function(){
var id = Random.id();
Meteor.call('create', id);
return id;
}
Meteor.methods({
create: function (id) {
Objects.insert({_id: id, name:'test'});
//more code...
}
});
//and in the Template...
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = create();
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});

Categories