ValueEventListener vs ChildEventListener for RecyclerView in Android

14,767

Solution 1

There are several concerns in this question (namely performance, progress indicator, handling new data such as sorting them). Naturally you should come up with a solution that takes into consideration the priority of your requirements. IMO both ValueEventListener and ChildEventListener have their use cases:

  1. ChildEventListener is generally the recommended way of sync'ing lists of objects. This is even mentioned in the documentation on working with lists:

    When working with lists, your application should listen for child events rather than the value events used for single objects.

    This is because your client only receives the changed child with the specific update (addition or removal) as opposed to the whole list on every update. Therefore, it allows a more granular level of handling of the updates to the list.

  2. ValueEventListener can be more useful when you need to process the entire list upon modification of a child. This is the case when you have to sort the list in your RecyclerView. It's much more easier to do this by getting the entire list, sorting it and refresh the data set of the view. On the other hand, using a ChildEventListener, it's more difficult to do the sorting because you only have access to one specific child in the list per update event.

From a performance point of view, given that even ValueEventListener is aware of the "delta" while sync'ing the update, I would tend to think of it as less efficient only on the client side, because the client has to do the processing on the entire list, but this is still much better than having the inefficiency at the network side.

Regarding constant refresh and progress indicators, the most important thing to note is that the Firebase database is a realtime database, hence a constant feed of realtime data is an inherent characteristic. If update events are not needed, you can simply use the addListenerForSingleValueEvent method to read the data only once. You can also use this if you want to display a progress indicator upon loading the first snapshot:

// show progress indicator
postsReference.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // load initial data set
        // hide progress indicator when done loading
    }

    ...
});

Solution 2

I had the same exact problem (ValueEventListener vs ChildEventListener for RecyclerView in Android).

The solution I used was to combine both this way :

Suppose dbref points to a firebase db location where my posts are.

  1. Use vel (ValueEventListener) to populate the recyclerview with the list of current data at dbref. Something like this: vel = dbref.addValueEventListener(vel);
  2. Once i have the list of current data at dbref, I remove the listener vel from dbref : dbref.removeEventListener(vel);. This is not necessary if you used dbref.addListenerForSingleValueEvent(vel); in step 1.
  3. Write a Query query to filter new posts inserted at dbref from now on. Something like this: query = dbref.orderByChild(post.createdat).startAt(System.currentTimeMillis())
  4. Attach a cel (ChildEventListener) to query to receive only new posts : query.addChildEventListener(cel);

Solution 3

I would anyday use onchildAdded listener in this case, there are several advantages of it, firstly you have figured out the network operation that is suppose there are 100 posts and even if one gets changed you will get callback of all of them. Whereas in onChildListener you will get callback of the only post that got changed. So what you can do is map the callbacks of firebase with the recycler views methods like this::-

onChildAdded --> you must cast the dataSnapshot into your class and call recyclerView.notifyItemAdded()

onChildRemoved --> recyclerView.notifyItemRemoved(int)

onChildChanged --> recyclerView.notifyItemChanged(int)

onChildMoved --> (you probably don't need this, it is for ordering/priority)

Solution 4

When working with collection data on Firebase you should be using:

  • onChildAdded
  • onChildChanged
  • onChildRemoved
  • onChildMoved

You may not need all of these but I've found that usually onChildAdded and onChildRemoved are often essential. You will have to sort and keep track of the data changing in your adapter and pass that back to the RecyclerView on every updated child.

In contrast, you can use the ValueEventListener and simply update the entire list however like you said, it would mean every update in that list will cause all the objects in the collection to be sent to you which will cost your user data and cost you more bandwidth on Firebase. This isn't recommended if your data will be updating often.

Source

Share:
14,767

Related videos on Youtube

Nominalista
Author by

Nominalista

I'm focused on mobile development. In my free time, I love to watch how apps are designed.

Updated on March 16, 2020

Comments

  • Nominalista
    Nominalista over 4 years

    Firebase Database users know that there are two basic listeners for listening Data: ValueEventListener and ChildEventListener. It works great when we listen for one object, but becomes quite difficult when we listen for some collection.

    To specify question, let's imagine that we have HackerNews feed, and we listen e.g. "posts" object in Firebase.

    Of course we have RecyclerView in our app for displaying posts and I think good idea would be using FirebaseUI, but the problem is that we want to make more abstract application in case of changing server side or tests. So we would use some adapters, but this is another question.

    We have two listeners as I mentioned, question is which is better?

    When we use ValueEventListener we will get whole collection, but in case of any changes, like one user changed content of the post, we will have to reload whole data, which means more bytes sending via expensive network transfer. Another problem is when we use multilisteners, and here is example:

    Post has userId, but we want to display his name, so in onDataChanged method we have fetch user data, like this:

    postsReference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            for (DataSnapshot data : dataSnapshot.getChildren()) {
                Post post = data.getValue(Post.class);   
                usersReference.child(post.getUserId()).addListenerForSingleValueEvent(new ValueEventListener() {
    
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        // Here we have user data
                    }
    
                    @Override
                    public void onCancelled(FirebaseError firebaseError) {
    
                    }
                });
            }
        }
    
        @Override
        public void onCancelled(FirebaseError firebaseError) {
        }
    });
    

    You can see that now we have to add each post to RecyclerView separately, which would give us clue that maybe we should use ChildEventListener.

    So now when we use ``ChildEventListener the problem is the same - we have to add each post to RecyclerView separately but when someone changes post content, firebase sends to us only this one post, which means less data via network.

    We don't like adding post separately to RecyclerView, because e.g.: - It's hard to add loading indicator, cause we don't know when all data comes. - Users gets constantly refreshing view, while new posts comes instead of whole lists becomes visible. - It's hard to sort that collection, we will have to do that maybe in adapter.

    Question

    What are best practices for using firebase with collection, and maybe better solutions than I wrote above?

    EDIT

    Data scheme would look like this:

    "posts" : {
        "123456" : {
            "createdAt" : 1478696885622,
            "content" : "This is post content",
            "title" : "This is post title",
            "userId" : "abc"
        },
        "789012" : {
            "createdAt" : 1478696885622,
            "content" : "This is post content 2",
            "title" : "This is post title 2",
            "userId" : "efg"
        }
    }
    "users" : {
        "abc" : {
            "name" : "username1"
        },
        "efg" : {
            "name" : "username2"
        }
    }
    

    EDIT 2

    I made a mistake -> Firebase is not fetching whole data in ValueEventListener when something has changed. It gets only "delta", here is proof.

    • Merlí Escarpenter Pérez
      Merlí Escarpenter Pérez over 7 years
      Can you show me your database schema?
    • Nominalista
      Nominalista over 7 years
      I edited question.
    • Ali Bdeir
      Ali Bdeir over 7 years
      None of the ones you mentioned. The FirebaseRecyclerAdapter from the Firebase UI is the best in this condition
    • Nominalista
      Nominalista over 7 years
      As I said in question, it's quite bad idea if you want to write more abstract code.
    • Ali Bdeir
      Ali Bdeir over 7 years
      @ThirdMartian if you name why I'd surely be convinced ad be on my way. Adding a progress indicator is very easy when using the FirebaseRecyclerAdapter
    • Nominalista
      Nominalista over 7 years
      I don't know what you mean by "if you name why I'd surely be convinced ad be on my way"
    • Merlí Escarpenter Pérez
      Merlí Escarpenter Pérez over 7 years
      When you say write abstract code you talk about change database schema?
    • Nominalista
      Nominalista over 7 years
      No, I mean code that you can replace server implementation, which means that we should write code which is abstract and change specific implementation easily. Especially when this implementation can change, like Firebase Api whould be changed.
    • Merlí Escarpenter Pérez
      Merlí Escarpenter Pérez over 7 years
      Next question... In your list of posts show this information or just a part?
    • Nominalista
      Nominalista over 7 years
      List of posts shows everything from post + username. We can change it, it is just sample.
    • Merlí Escarpenter Pérez
      Merlí Escarpenter Pérez over 7 years
      Normally when you show a list of post, you show a little preview, e. g. title, user, datetime and likes... In this preview you don't need really a content information. Normally if user wanna read a post open just one, no more. Why I said that? It would be good have in one node the information of post preview and especific information of content in other node. When user select the post preview just get this information from firebase. This helps you to reduce data and time to get information from firebase... You following me? :)
    • Merlí Escarpenter Pérez
      Merlí Escarpenter Pérez over 7 years
      Then is not too expensive control a big collections with ValueEventListener()...
    • Nominalista
      Nominalista over 7 years
      But you are wondering about how application works, it's common situation when you have some data and you have to fetch more data, I can change example to fitting such situation. So please follow this example above.
  • Nominalista
    Nominalista over 7 years
    Please see my Edit 2
  • Nitish K.
    Nitish K. over 7 years
    Firebase will return the entire data snapshot of where your ValueEventListener was attached anytime a child is changed. Source
  • Nominalista
    Nominalista over 7 years
    Yeah, but it won't download it entirely, I just get entire data snapshot but database internally will send only "delta"
  • Nitish K.
    Nitish K. over 7 years
    Oh I understand what you meant now. I would still stand by using child listeners due to the fact that you don't need to iterate the entire snapshot as this can be quite a lot if you have a large list. Specifically in your situation, as I understand, you don't want the view to be constantly updating as the listeners are called so maybe you could keep a constantly updated data set but only refresh the view when the user wants to refresh,
  • Aliton Oliveira
    Aliton Oliveira almost 5 years
    @NitishKasturia you can use ListAdapter, once you provide the whole snapshot the AsyncDiffUtil will take care of the rest by comparing the two lists (old and new ones).
  • DragonFire
    DragonFire about 4 years
    sorting -> use value event listener... how about cost is there any difference
  • Sourav Kannantha B
    Sourav Kannantha B over 3 years
    This is what I thought of.. But it has thin gap first call and removal of valueEventListener and attaching a childEventListener. If something was changed during this time (narrow but not zero), then I think you will miss that. If there was a way to simultaneously add both, then it would be much useful.