7

If I have a list of 50,000 items stored in my firebase reference, and 5 items have been added to that list since the last time the client was online and listening, which callback would I have to use such that it is only triggered for the 5 new items that have been added?

I have offline persistence enabled on my client with Firebase.getDefaultConfig().setPersistenceEnabled(true);. I have a listener bound to an activity listening for children added to a reference. Everytime the activity is created onChildAdded is called for all the data in the reference. Is it possible to make onChildAdded and onChildRemoved be called only for "diff" between my local cache and the data on the firebase server? Or if that's not possible, then to trigger only those onChild* callbacks after the most recent update from firebase?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Vinay Nagaraj
  • 1,162
  • 14
  • 26
  • Have you already tried using the onComplete callback for [update()](https://www.firebase.com/docs/web/api/firebase/update.html)? – not_a_bot Jan 15 '16 at 00:40
  • From the android documentation, it looks like updateChildren() is similar to setValue(). I don't know how to use that to figure out what I'm trying to do. I've added an example to the question which might make what I'm trying to achieve clearer. – Vinay Nagaraj Jan 16 '16 at 14:22

2 Answers2

13

Everytime the activity is created onChildAdded is called for all the data in the reference. Is it possible to make onChildAdded and onChildRemoved be called only for "diff" between my local cache and the data on the firebase server?

No, this is not possible. From the documentation on event types:

child_added is triggered once for each existing child and then again every time a new child is added to the specified path

Now back to your initial question:

which callback would I have to use such that it is only triggered for the 5 new items that have been added?

That would be:

ref.limitToLast(5)...

But this requires that you know how many items were added to the list, since you last listened.

The more usual solution is to keep track of the last item you've already seen and then use startAt() to start firing events from where you last were:

ref.orderByKey().startAt("-Ksakjhds32139")...

You'd then keep the last key you've seen in shared preferences.

Similarly you can keep the last time the activity was visible with:

long lastActive = new Date().getTime();

Then add a timestamp with Firebase.ServerValue.TIMESTAMP to each item and then:

ref.orderByChild("timetstamp").startAt(lastActive+1)...
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Would this solution also fire childChanged and childRemoved events? – Vinay Nagaraj Jan 19 '16 at 12:11
  • Although Frank's answer is ABSOLUTELY CORRECT, I think you should reconsider the logic in order to handle CREATE, UPDATE and DELETE (based on the actions he suggests). It looks to me that you may consider a Firebase based FIFO journal of C(R)UD actions (with expiration / eviction logic) in order to catch everything that happened when different devices were off-line. – seanpj Jan 19 '16 at 12:12
  • Yes, a FIFO of events since last query would be optimal, but it looks like I'm going to use a childEventListener as is. When capturing events, I'll ignore data that has already been seen by storing it locally. – Vinay Nagaraj Jan 19 '16 at 12:54
  • What about deleted items when off-line? – seanpj Jan 19 '16 at 13:33
  • I'm not sure how, but the onChildRemoved seems to fires correctly. I kill all processes on my android phone (application should no longer be listening to firebase), delete an item from the list using my firebase app dashboard, start the application on my phone and navigate to the relevant activity. The item I deleted appears for a second before disappearing from the list. I'm okay with this behavior for now because data would be deleted very rarely from this particular list, RC & U are the main use cases. I guess I should write some extensive tests though to really understand what's happening – Vinay Nagaraj Jan 19 '16 at 15:43
  • When you re-connect to the Firebase server, it synchronizes the current state of any data that has changed. The client then fires the corresponding `child_` and `value` events to ensure your view of the world is up-to-date with the server again. – Frank van Puffelen Jan 19 '16 at 15:48
  • Yes, I did multiple edits, added and deleted the same data multiple times and it called onChildChanged once. Thanks! – Vinay Nagaraj Jan 19 '16 at 15:58
  • That is correct: Firebase synchronizes state, not state changes. If you want to ensure that each client learns of every state change, you should store the actual state changes in the database. – Frank van Puffelen Jan 19 '16 at 15:59
  • 1
    I liked your last solution but what about the time differences between server and the local device? – sivi Mar 16 '17 at 13:23
1

You should use on('child_changed'). Normally, on() is used to listen for data changes at a particular location. However, on('child_changed') notifies you

when the data stored in a child (or any of its descendants) changes.

It will pass a data snapshot to the callback that contains the new child contents. Keep in mind that a single child_changed event may potentially represent multiple changes to the child.

not_a_bot
  • 2,332
  • 2
  • 19
  • 33
  • 2
    Wouldn't he in this case get a list of all 50000 + 5 items not he 5 he is asking for (see the last paragraph of the question). – seanpj Jan 18 '16 at 10:35
  • 1
    You're right, I suppose he could also use [limitToLast(x)](https://www.firebase.com/docs/web/api/query/limittolast.html) to get the last X updated children. – not_a_bot Jan 18 '16 at 23:33