60

From the Firebase API:

Child Added: This event will be triggered once for each initial child at this location, and it will be triggered again every time a new child is added.

Some code:

listRef.on('child_added', function(childSnapshot, prevChildName) {
    // do something with the child
});

But since the function is called once for each child at this location, is there any way to get only the child that was actually added?

Matt Robertson
  • 2,928
  • 5
  • 34
  • 62

6 Answers6

46

To track things added since some checkpoint without fetching previous records, you can use endAt() and limit() to grab the last record:

// retrieve the last record from `ref`
ref.endAt().limitToLast(1).on('child_added', function(snapshot) {

   // all records after the last continue to invoke this function
   console.log(snapshot.name(), snapshot.val());

});
Nuri YILMAZ
  • 4,291
  • 5
  • 37
  • 43
Kato
  • 40,352
  • 6
  • 119
  • 149
  • 1
    Actually, I discovered a simpler approach, since it continues to notify you of added events even after limit() is reached. I've updated my answer accordingly. – Kato Aug 03 '12 at 07:38
  • 2
    It's unclear what you mean. This works only on ordered sets, but if you need ordering for something else (besides an ordered set?)... Also, your comment doesn't help anybody understand how to get child added events in Firebase. – Kato Jul 22 '14 at 19:28
  • 1
    The callback function fires only once for the record returned by `ref.endAt().limit(1)`. It does not fire for other new records added after that record. I'm testing this with Angularfire 0.8.0 – Waseem Sep 10 '14 at 20:22
  • 3
    Kato has a really good explanation on GitHub, [here](https://gist.github.com/katowulf/6383103). – Lindauson Mar 01 '16 at 20:59
  • Great link! Written a couple years after this Stack Overflow question. Queries and limitToLast() didn't exist in '12, of course. – Kato Mar 02 '16 at 03:10
  • 1
    That would still call the callback once for the last child (before new children are added), right? – Rodrigo Ruiz Jun 02 '16 at 02:59
  • I don't get it - wouldn't this just get the last item? What about "only items added after binding to this event"? – Dominic Jun 12 '16 at 20:27
  • For now I'm using a date property to ensure check in the future – Dominic Jun 12 '16 at 21:00
38

limit() method is deprecated. limitToLast() and limitToFirst() methods replace it.

// retrieve the last record from `ref`
ref.limitToLast(1).on('child_added', function(snapshot) {

   // all records after the last continue to invoke this function
   console.log(snapshot.name(), snapshot.val());
   // get the last inserted key
   console.log(snapshot.key());

});
kamleshpal
  • 33
  • 8
tibeoh
  • 460
  • 6
  • 11
  • 6
    I've tried this approach, but one thing to look out for: if the last record is removed, for example a user posts a comment and removes it right away (it was a mistake for example) the above .on() function will be called again. I solved adding a timestamp and checking if the child that was added is less than one second old, if it's older, it's an old record and wasn't added. For this have a look at ref.child('.info') – DivZero Oct 17 '15 at 13:51
  • 4
    I don't get it - wouldn't this just get the last item? What about "only items added after binding to this event"? – Dominic Jun 12 '16 at 20:27
  • Yes wondering the same. It just gets the last item once and then does not fire for other added events. – Orlando Jul 07 '16 at 08:11
  • @DivZero I tried to do what you said, but since my chat is shared between android and iOS it didn't work because messages from other devices have a longer delay. It's also error-prone, so instead I checked if the new message is already in chat, if it's I don't add it. – Pietro Coelho Apr 02 '18 at 15:58
  • The `child_added` function takes two parameters, `snapshot` and `prevChildKey`, with this approach `prevChildKey` is always null. I used `ref.endAt().on('child_added', function(snapshot, prev) {});` – av4625 Sep 26 '18 at 18:25
7

Since calling the ref.push() method without data generates path keys based on time, this is what I did:

// Get your base reference
const messagesRef = firebase.database().ref().child("messages");

// Get a firebase generated key, based on current time
const startKey = messagesRef.push().key;

// 'startAt' this key, equivalent to 'start from the present second'
messagesRef.orderByKey().startAt(startKey)
.on("child_added", 
    (snapshot)=>{ /*Do something with future children*/}
);

Note that nothing is actually written to the reference(or 'key') that ref.push() returned, so there's no need to catch empty data.

smackjax
  • 91
  • 1
  • 3
4

I tried other answers way but invoked at least once for last child. If you have a time key in your data, you can do this way.

ref.orderByChild('createdAt').startAt(Date.now()).on('child_added', ...
ton1
  • 7,238
  • 18
  • 71
  • 126
  • [2018-04-27T23:58:32.727Z] @firebase/database: FIREBASE WARNING: Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "createdAt" at /sms-log to your security rules for better performance. – supergentle Apr 27 '18 at 23:59
1

Swift3 Solution:

You can retrieve your previous data through the following code:

queryRef?.observeSingleEvent(of: .value, with: { (snapshot) in
    //Your code
})

and then observe the new data through the following code.

queryRef?.queryLimited(toLast: 1).observe(.childAdded, with: { (snapshot) in
    //Your Code
})
Zain
  • 37,492
  • 7
  • 60
  • 84
Milligator
  • 159
  • 1
  • 10
  • Can you expand on this so that .childAdded does not add all messages. So, if I'm using an array I want to query to the last 100 at observeSingleEvent, and then after that listen for all new events at .chlidAdded & .childRemoved. – Luke Irvin Dec 11 '18 at 04:00
  • If I understand your question correctly, you want to retrieve last 100 record at the beginning and then you want to listen for the updates. Based on Google documents, you can use "queryLimitedToLast" observer in this matter. https://firebase.google.com/docs/database/ios/lists-of-data – Milligator Dec 18 '18 at 19:32
0

I found tibeoh's answer to be effective, with one caveat (details got too wordy for a comment).

For those saying that his answer only triggers on the first element and no follow-up inserts: In my tests I found this is true IF you don't insert records that would come after the most recently inserted records by key. So this hook apparently works strictly on the key's sort order and not based on what insertion events happen after the hook is initialized.

Eg:
last sorted key at time of binding: "b"
insert at "a" -> no trigger (comes before "b")
insert at "bbb" -> triggered
insert at "bb" -> no trigger (comes before "bbb")
insert at "bbbb" -> triggered

Workaround: use push() for auto-generated keys based on server time. This will ensure that all keys get inserted after anything prior to it according to sort order, and thus will always trigger as it should.

DJ_R
  • 207
  • 2
  • 6