Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically change the limit of a Firebase Query Reference on Android

I am using Firebase for the chat feature in my Android application. What I am aiming to support is:

  1. live updates whenever a new message arrives or an existing message changes or gets deleted and
  2. infinite scroll backwards in the history of messages.

The first part seems easy. I create a firebase reference with a limit.

ref = new Firebase(FIREBASE_URL).child("chat").limitToFirst(50);

This reference is then used by an Adapter which adds a listener. And then modifies a List whenever data changes.

ref.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {...}
    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String s) {...}
    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {...}
    ...

The second part is where I struggle to find the right approach. When the user arrives at the end of the list, I want to load older messages and add them to the list of messages (infinite scroll).

The preferred solution would be to simply call something like ref.changeLimitToFirst(100) but such a method does not exist.

What would be the best approach to solve this with firebase?


Edit:

I deleted the solutions initially suggested by me because they did not help clarifying the problem. Instead, I will list new solutions below which I found since I originally asked the question.

If you know of a better, more elegant approach, please leave an answer!

  1. The easiest approach seems to be to remove the listener from the old Query; Then create a new Query with a new limit. An example for this approach can be found in this pull request for the Firebase-UI-Android lib. Downsides are:
    • After adding the new listener you will receive child_added event for each child, not only for the ones you are trying to load. Therefore you have to check if a child already exists in the ArrayList before adding it. This means there will be a lot of iterations if your list gets longer.
    • You don't know when loading is completed. A workaround for this is to add an additional value listener, which will be fired once the complete snapshot has been loaded (and thus loading of the new page is completed).
  2. The second approach would be to load each page separately by listening to the value event. Unfortunately, something like offset() does not exist. So the way to go is to remember the last item you loaded and pass it to the startAt method. Downsides:
    • In order to receive updates you'd need to remove the old child listener and add a new one (because the limit changes) each time you'll load a new page. This listener will then receive child_added events which you have to filter out (see above).
    • The position of the last item might change between the time when you load the first page and the time you load and add the second page. If the order of your data might change, you can not be sure that child you saved still is the correct reference for your next Query. In this case you need, again, a child listener to receive updates on the data you previously loaded.
like image 381
marcorei Avatar asked Sep 04 '15 11:09

marcorei


1 Answers

In Firebase there are two ways to retrieve data:

1) Observe a node via an EVENT

2) Query (within) a node for specific data

The two items are independent functions, but can behave in similar ways and can both retrieve data from Firebase.

With Firebase queries, you can selectively retrieve data based on various factors.

The answer to the question depends on the structure of the data. Assume this structure

Msgs
  node_ref:
    index: 0
    msg: 'Hows the weather'
  node_ref:
    index: 1
    msg: 'Hot'

Answer to: 1) Live Updates

If the Msgs node has a listener, the app is notified of any changes to that node and could be used to load an initial snapshot of the data within that node and then listen for add's, updates and removes to messages within that node.

Based on your update, you have a way to load the first 50. However, the answer to part #2 may be another solution.

Answer to: 2) scrolling backwards (or forwards, or loading in sets of data)

Adding a listener would be independent of performing a Firebase Query on the Msgs node for specific messages. A Query would be used to load in a message with a child index of 1, or any message with child index of less than 10 or messages that contain child indexes in a range of starting at 5 and ending at 10.

This could be done with a Range Query: Using startAt(), endAt(), and equalTo() allows you to choose arbitrary starting and ending points for queries.

So if the Msgs node contained 100 messages, and messages with a child index of 90-99 are displayed and the user scrolls backward (to older messages) a simple Range Query could be done to query for messages 80-89, which would then be loaded and displayed. Similarly if messages 0-9 are displayed (the oldest) and the user scrolls forward, Range Query from messages 10-19.

As you can see, a listener is not needed at all when leveraging Queries to load the data. The listener would be used to tell the app when there is new data (added, updated or deleted)

That being said, there are ways to do this with no queries but it would depend on the structure of the data and how you want to UI to behave.

like image 113
Jay Avatar answered Sep 16 '22 21:09

Jay