22

I know there are lot of ways to have an empty view for a RecyclerView. But my question is for FirebaseRecyclerView.

My layout is:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
    android:id="@+id/feed_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical" />

<ProgressBar
    android:id="@+id/feed_loading"
    style="?android:attr/progressBarStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:visibility="gone"/>
</RelativeLayout>

So I am showing Loading ProgressBar before the RecyclerView fills its items. Now what if server doesn't have any item. In this situation my RecyclerView is empty always and my Loading ProgressBar always visible to user.

So instead of showing the ProgressBar for indefinite period, I want to show some empty layout. E.g. "No Data Found" or something similar message.

The final result should be: ProgressBar should be shown until data is loaded to RecyclerView and once data is loaded ProgressBar should be invisible. But if no data is present in server, some empty layout should be shown instead of ProgressBar.

In normal RecyclerView, we have a dataset (some ArrayList, etc) and if it is empty then we can show that empty layout. But in case of FirebaseRecyclerAdapter, I dont have the reference of Snapshot in my Activity or Context. Nor I have any callback which tells me that no data is present in server.

Any workaround will help a lot.

Chandra Sekhar
  • 18,914
  • 16
  • 84
  • 125

2 Answers2

27

Here is what I would try. First check out the accepted answer to the question linked below. It provides some very good insight into how Firebase queries work. I'd consider the info trusted since the answer is by someone on the Firebase team:

How to separate initial data load from incremental children with Firebase?

So, based on the answer to the question linked above and the fact that the FirebaseRecyclerAdapter is backed by a FirebaseArray which is populated using a ChildEventListener I would add a Single value event listener on the same database reference used to populate your FirebaseRecyclerAdapter. Something like this:

//create database reference that will be used for both the
//FirebaseRecyclerAdapter and the single value event listener
dbRef = FirebaseDatabase.getInstance().getReference();

//setup FirebaseRecyclerAdapter
mAdapter = new FirebaseRecyclerAdapter<Model, YourViewHolder>(
                Model.class, R.layout.your_layout, YourViewHolder.class, dbRef) {

                @Override
                public void populateViewHolder(YourViewHolder holder, Model model, int position){

                     //your code for populating each recycler view item

                };

mRecyclerView.setAdapter(mAdapter);

//add the listener for the single value event that will function
//like a completion listener for initial data load of the FirebaseRecyclerAdapter
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
             @Override
             public void onDataChange(DataSnapshot dataSnapshot) {
                 //onDataChange called so remove progress bar

                 //make a call to dataSnapshot.hasChildren() and based 
                 //on returned value show/hide empty view 

                 //use helper method to add an Observer to RecyclerView    
             }

             @Override
             public void onCancelled(DatabaseError databaseError) {

             }
      });

That would handle the initial setup of the RecyclerView. When onDataChange is called on the single value event listener use a helper method to add an observer to the FirebaseRecyclerAdapter to handle any subsequent additions/deletions to database location.

mObserver = new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                //perform check and show/hide empty view
            }

            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                //perform check and show/hide empty view
            }
           };
mAdapter.registerAdapterDataObserver(mObserver);
Community
  • 1
  • 1
Kevin O'Neil
  • 1,411
  • 1
  • 15
  • 15
  • Thanks for the solution, its a great trick and worked for me. – Chandra Sekhar Aug 21 '16 at 05:04
  • No problem. Glad to help. – Kevin O'Neil Aug 21 '16 at 10:16
  • I can't seem to check if onItemRangeRemoved() resulted in an empty list. What's the difference between the AdapterDataObserver and just calling dbRef.addValueEventListener()? – Justin Liu Dec 06 '16 at 09:53
  • 1
    @JustinLiu - keep in mind, by design the SingleValueEvent listener is only triggered once after which it will never be called again. So the ValueEventListener will only be used only for the initial setup of your FirebaseRecyclerAdapter to see if it has any items. Now, imagine the initial setup is complete and the user is still looking at the screen when the last item is suddenly removed from the RecyclerView, you need a way to react to that otherwise the user will just see a blank screen. That's what the DataObserver is used for - to react to any data set changes after the initial setup. – Kevin O'Neil Dec 06 '16 at 17:52
  • @JustinLiu - onItemRangeRemoved will be called anytime an item is removed from the RecyclerAdapter so you can do something like make a call to mAdapter.getItemCount()....if it is 0 then show the empty view. – Kevin O'Neil Dec 06 '16 at 17:55
  • @KevinO'Neil Okay, so I know about SingleValueEvent, but won't the other method, addValueEventListener() keep listening? So even when the last item is removed, it will be fired, which then you can again grab the snapshot and check its children count with the same method. – Justin Liu Dec 07 '16 at 02:00
  • @JustinLiu That's correct, addValueEventListener() would keep listening until it was removed but that's not a concern in the code above because only addSingleValueEventListener() is used. – Kevin O'Neil Dec 07 '16 at 03:27
8

Answer from: https://stackoverflow.com/a/40204298/2170278

the FirebaseUI adapters nowadays have an onDataChanged() method that you can override to detect when they're done loading a set of data.

See the source code on github. From there:

This method will be triggered each time updates from the database have been completely processed. So the first time this method is called, the initial data has been loaded - including the case when no data at all is available. Each next time the method is called, a complete update (potentially consisting of updates to multiple child items) has been completed.

You would typically override this method to hide a loading indicator (after the initial load) or to complete a batch update to a UI element.

The FirebaseUI sample app overrides onDataChanged() to hide its "loading" indicator:

public void onDataChanged() {
    // If there are no chat messages, show a view that invites the user to add a message.
    mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
Community
  • 1
  • 1