I am trying to remove an item from $firebaseArray (boxes).
The remove funcion:
function remove(boxJson) {
return boxes.$remove(boxJson);
}
It works, however it is immediately added back:
This is the method that brings the array:
function getBoxes(screenIndex) {
var boxesRef = screens
.child("s-" + screenIndex)
.child("boxes");
return $firebaseArray(boxesRef);
}
I thought perhaps I'm holding multiple references to the firebaseArray and when one deletes, the other adds, but then I thought firebase should handle it, no?
Anyway I'm lost on this, any idea?
UPDATE
When I hack it and delete twice (with a timeout) it seems to work:
function removeForce(screenIndex, boxId) {
setTimeout(function () {
API.removeBox(screenIndex, boxId);
}, 1000);
return API.removeBox(screenIndex, boxId);
}
and the API.removeBox:
function removeBox(screenIndex, boxId) {
var boxRef = screens
.child("s-" + screenIndex)
.child("boxes")
.child(boxId);
return boxRef.remove();
}
When you remove something from firebase it is asynchronous. Per the docs the proper way to remove an item is from firebase, using AngularFire is:
var obj = $firebaseObject(ref);
obj.$remove().then(function(ref) {
// data has been deleted locally and in the database
}, function(error) {
console.log("Error:", error);
});
$remove() ... Removes the entire object locally and from the database. This method returns a promise that will be fulfilled when the data has been removed from the server. The promise will be resolved with a Firebase reference for the exterminated record.
Link to docs: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-remove
The most likely cause is that you have a security rules that disallows the deletion.
When you call boxes.$remove Firebase immediately fires the child_removed event locally, to ensure the UI is updated quickly. It then sends the command to the Firebase servers to check it and update the database.
On the server there is a security rule that disallows this deletion. The servers send a "it failed" response back to the client, which then raises a child_added event to fix the UI.
Appearantly I was saving the items again after deleting them. Clearly my mistake:
function removeSelected(boxes) {
var selectedBoxes = Selector.getSelectedBoxes(boxes);
angular.forEach(selectedBoxes, function (box) {
BoxManager.remove(box);
});
Selector.clearSelection(boxes, true);
}
In the clearSelection method I was updating a field on the boxes and saved them again.
Besides the obvious mistake this is a lesson for me on how to work with Firebase. If some part of the system keeps a copy of your deleted item, saving it won't produce a bug but revive the deleted item.
For those, who have the similar issue, but didn't solve it yet.
There are two methods for listening events: .on() and .once(). In my case that was the cause of a problem.
I was working on a migration procedure, that should run once
writeRef
.orderByChild('text_hash')
.equalTo(addItem.text_hash)
.on('value', val => { // <--
if (!val.exists()) {
writeRef.push(addItem)
}
});
So the problem was exactly because of .on method. It fires each time after a data manipulation from FB's console.
Changing to .once solved that.
Related
How can I listen to a specific field change with firestore js sdk ?
In the documentation, they only seem to show how to listen for the whole document, if any of the "SF" field changes, it will trigger the callback.
db.collection("cities").doc("SF")
.onSnapshot(function(doc) {
console.log("Current data: ", doc && doc.data());
});
You can't. All operations in Firestore are on an entire document.
This is also true for Cloud Functions Firestore triggers (you can only receive an entire document that's changed in some way).
If you need to narrow the scope of some data to retrieve from a document, place that in a document within a subcollection, and query for that document individually.
As Doug mentioned above, the entire document will be received in your function. However, I have created a filter function, which I named field, just to ignore document changes when those happened in fields that I am not interested in.
You can copy and use the function field linked above in your code. Example:
export const yourCloudFunction = functions.firestore
.document('/your-path')
.onUpdate(
field('foo', 'REMOVED', (change, context) => {
console.log('Will get here only if foo was removed');
}),
);
Important: The field function is not avoiding your function to be executed if changes happened in other fields, it will just ignore when the change is not what you want. If your document is too big, you should probably consider Doug's suggestion.
Listen for the document, then set a conditional on the field you're interesting in:
firebase.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Castilian-female-IBM').onSnapshot(function(snapshot) {
if (snapshot.data().audioFiles) { // eliminates an error message
if (snapshot.data().audioFiles.length === 2) {
audioFilesReady++;
if (audioFilesReady === 3) {
$scope.showNextWord();
}
}
}
}, function(error) {
console.error(error);
});
I'm listening for a document for a voice (Castilian-female-IBM), which contains an array of audio files, in webm and mp3 formats. When both of those audio files have come back asynchronously then snapshot.data().audioFiles.length === 2. This increments a conditional. When two more voices come back (Castilian-male-IBM and Latin_American-female-IBM) then audioFilesReady === 3 and the next function $scope.showNextWord() fires.
Just out of the box what I do is watching before and after with the before and after method
const clientDataBefore = change.before.data();
console.log("Info database before ", clientDataBefore);
const clientDataAfter = change.after.data();
console.log("Info database after ", clientDataAfter );
For example now you should compare the changes for a specific field and do some actions or just return it.
Some more about before.data() and after.data() here
I have written firebase cloud function to trigger on update record. sometimes I am not getting the same record which is updating. I am adding my code below.Please check attached image also.
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}/userResponse').onUpdate(event => {
return admin.database().ref(`/Notification/${event.params.userId}/${event.params.notificationId}`).once('value').then(function (snapshot) {
var notification = snapshot.val();
if (!notification) {
console.error("Notification not found on notification update");
return;
};
I can also get Notification object from the parent but I want to know issue best approach and the problem with this code.
this is error log
this is database structure
This is my 1st post here please let me know if need more information.
Thanks
You don't have to call once within the Function since it is already returning the data at the location you are listening to, just listen to the parent node.
So you should do like:
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}').onUpdate(event => {
const notification = event.data.val();
if (notification === null) {
console.error("Notification not found on notification update");
return null;
//actually this would only be called in case of deletion of the Notification
} else {
//do something with the notification data: send Android notification, send mail, write in another node of the database, etc.
//BUT return a Promise
//notification const declared above is a JavaScript object containing what is under this node (i.e. a similar structure than your database structure as shown in the image within your post.)
}
});
I would suggest that you have a look at these three videos from the Firebase team:
https://www.youtube.com/watch?v=7IkUgCLr5oA&t=517s
https://www.youtube.com/watch?v=652XeeKNHSk&t=27s
https://www.youtube.com/watch?v=d9GrysWH1Lc
Also, note that Cloud Functions have been updated and the first line of your code shall be written differently if you are using a CF version above 1.0.0. See https://firebase.google.com/docs/functions/beta-v1-diff
I need to fetch sub-set of documents in Firestore collection modified after some moment. I tried going theses ways:
It seems that native filtering can work only with some real fields in stored document - i.e. nevertheless Firestore API internally has DocumentSnapshot.getUpdateTime() I cannot use this information in my query.
I tried adding my _lastModifiedAt 'service field' via server-side firestore cloud function, but ... that updating of _lastModifiedAt causes recursive invocation of the onWrite() function. I.e. is does also not work as needed (recursion finally stops with Error: quota exceeded (Function invocations : per 100 seconds)).
Are there other ideas how to filter collection by 'lastModifiedTime'?
Here is my 'cloud function' for reference
It would work if I could identify who is modifying the document, i.e. ignore own updates of _lastModified field, but I see no way to check for this
_lastModifiedBy is set to null because of current inability of Firestore to provide auth information (see here)
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
console.log(event.data.data());
var lastModified = {
_lastModifiedBy: null,
_lastModifiedAt: now
}
return event.data.ref.set(lastModified, {merge: true});
});
I've found the way to prevent recursion while updating '_lastModifiedAt'.
Note: this will not work reliably if client can also update '_lastModifiedAt'. It does not matter much in my environment, but in general case I think writing to '_lastModifiedAt' should be allowed only to service accounts.
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
var doc = event.data.data();
var prevDoc = event.data.previous.data();
if( doc && prevDoc && (doc._lastModifiedAt != prevDoc._lastModifiedAt) )
// this is my own change
return 0;
var lastModified = getLastModified(event);
return event.data.ref.set(lastModified, {merge: true});
});
Update: Warning - updating lastModified in onWrite() event causes infinite recursion when trying to delete all documents in Firebase console. This happens because onWrite() is also triggered for delete and writing lastModified into deleted document actually resurrects it. That document propagates back into console and is tried to be deleted once again, indefinitely (until WEB page is closed).
To fix that issue above mentioned code has to be specified individually for onCreate() and onUpdate().
How about letting the client write the timestamp with FieldValue.serverTimestamp() and then validate that the value written is equal to time in security rules?
Also see Mike's answer here for an example: Firestore Security Rules: If timestamp (FieldValue.serverTimestamp) equals now
You could try the following function, which will not update the _lastModifiedAt if it has been marked as modified within the last 5 seconds. This should ensure that this function only runs once, per update (as long as you don't update more than once in 5 seconds).
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
console.log(event.data.data());
if ((Date.now() - 5000) < event.data.data()._lastModifiedAt) {return null};
var lastModified = {
_lastModifiedBy: null,
_lastModifiedAt: now
}
return event.data.ref.set(lastModified, {merge: true});
});
I´m starting with IndexedDB and to not reinvent the wheel I´m using Dexie.js https://github.com/dfahlander/Dexie.js
I created the database, I added data and now I´m creating a generic function that get a CSV and populate the database in anothers tables.
So, more or less my code is
// Creation and populate database and first table
var db = new Dexie("database");
db.version(1).stores({table1: '++id, name'});
db.table1.add({name: 'hello'});
Until here all is OK
Now, in success of ajax request
db.close();
db.version(2).stores({table2: '++id, name'});
db.open();
db.table2.add({name: 'hello'});
First time this code run everything is OK, but next time I get this error
VersionError The operation failed because the stored database is a
higher version than the version requested.
If I delete database and run code again only first time works OK.
Any idea? I don´t like too much IndexedDB version way, it´s looks frustrating and I don't get lot of help in the Net
Thanks.
Edit:
I discover the ¿problem/bug/procedure?. If I don´t add nothing before any version modification I haven't this issue, but does somebody know if is this the normal procedure?
So.. if this is the procedure I can't add any table dinamycally with a generic method. First all declarations and then add values. Any possibility to add a table after add values?
Edit again... I just realized that I could create another database. I'll post results. But any information about this issue is welcome :)
Edit again... I created dinamycally another database and everybody is happy!!
That is because the second time the code runs, your database is on version 2, but your main code still tries to open it at version 1.
If not knowing the current version installed, try opening dexie in dynamic mode. This is done by not specifying any version:
var db = new Dexie('database');
db.open().then(function (db) {
console.log("Database is at version: " + db.verno);
db.tables.forEach(function (table) {
console.log("Found a table with name: " + table.name);
});
});
And to dynamically add a new table:
function addTable (tableName, tableSchema) {
var currentVersion = db.verno;
db.close();
var newSchema = {};
newSchema[tableName] = tableSchema;
// Now use statically opening to add table:
var upgraderDB = new Dexie('database');
upgraderDB.version(currentVersion + 1).stores(newSchema);
return upgraderDB.open().then(function() {
upgraderDB.close();
return db.open(); // Open the dynamic Dexie again.
});
}
The latter function returns a promise to wait until it's done before using the new table.
If your app resides in several browsers, the other windows will get their db connection closed as well so they can never trust the db instance to be open at any time. You might want to listen for db.on('versionchange') (https://github.com/dfahlander/Dexie.js/wiki/Dexie.on.versionchange) to override the default behavior for that:
db.on("versionchange", function() {
db.close(); // Allow other page to upgrade schema.
db.open() // Reopen the db again.
.then(()=> {
// New table can be accessed from now on.
}).catch(err => {
// Failed to open. Log or show!
});
return false; // Tell Dexie's default implementation not to run.
};
I have the list of items on page, I want to add listener for detect insert in collection.
The code below is on client side.
Messages.find().observeChanges({
added: function(){
console.log('Message added');
}
});
But if in collection already exist items, after load page will fire callback for all items. Why callback 'added' fired for items that already in collection and how to detect only really new inserts?
There are two solutions. For the first one, look here: https://github.com/oortcloud/unofficial-meteor-faq#why-does-observe-fire-a-bunch-of-added-events-for-existing-documents
The second one is this:
var isInitial = true
Messages.find().observeChanges({
added: function(){
if(!isInitial){
console.log('Message added');
}
}
});
isInitial = false
This works because the call to observeChanges will fire the added callback for the documents already in Minimongo before returning (and isInitial will be set to false).
You can try using collections hooks which will do exactly what you want:
https://atmospherejs.com/mrt/collection-hooks
Unfortunately this package hasn't yet been updated to work with Meteor 0.9 and later.