91

In RecyclerView, I want to set an empty view to be shown when the adapter is empty. Is there an equivalent of ListView.setEmptyView()?

Sheharyar
  • 73,588
  • 21
  • 168
  • 215
Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60
  • 1
    see [RecyclerView setEmptyView](http://www.tutorialforandroid.com/2014/09/recyclerview-setemptyview.html) probably help to achieve same – ρяσѕρєя K Dec 11 '14 at 02:20

14 Answers14

121

Here's a class similar to @dragon born's, but more complete. Based on this gist.

public class EmptyRecyclerView extends RecyclerView {
    private View emptyView;
    final private AdapterDataObserver observer = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkIfEmpty();
        }
    };

    public EmptyRecyclerView(Context context) {
        super(context);
    }

    public EmptyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    void checkIfEmpty() {
        if (emptyView != null && getAdapter() != null) {
            final boolean emptyViewVisible = getAdapter().getItemCount() == 0;
            emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE);
            setVisibility(emptyViewVisible ? GONE : VISIBLE);
        }
    }

    @Override
    public void setAdapter(Adapter adapter) {
        final Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(observer);
        }

        checkIfEmpty();
    }

    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
        checkIfEmpty();
    }
}
Marc Plano-Lesay
  • 6,808
  • 10
  • 44
  • 75
  • can u please explain how i can use this class? – Ololoking Apr 02 '15 at 14:29
  • Well exactly like you would do with a RecyclerView, it just adds the `setEmptyView` method, that you can call whenever you want to define the empty view. See the `ListView.setEmptyView` documentation if it's unclear, it's the same idea. – Marc Plano-Lesay Apr 03 '15 at 05:43
  • thanks for answer , i have read about `ListView.setEmptyView` / i just cant understand , i have already class RecyclerViewAdapter , i have fragment where i set the adapter , from this code , which u posted i need just to copy this funsctions to my RecyclerViewAdapter , or i need just call this `EmptyRecyclerView` class in my fragment? – Ololoking Apr 03 '15 at 08:26
  • You need to replace your `RecyclerView` instances by `EmptyRecyclerView` instances. Then, in your fragment, when you set up your UI, just call `setEmptyView` with the view you want to display when your adapter is empty. – Marc Plano-Lesay Apr 03 '15 at 09:17
  • What is the anchor of the empty view? My problem is that the empty view ignores layout_height="match_parent" – DarkLeafyGreen Aug 26 '15 at 19:12
  • @artworkad Well it's up to you. The only thing made here is switching the visibility. You could replace that by a ViewSwitcher and achieve the same result. I don't see any reason why the attribute would be ignored. – Marc Plano-Lesay Aug 27 '15 at 00:26
  • @Kernald I managed to achieve full height by setting layout params :) – DarkLeafyGreen Aug 27 '15 at 07:16
  • If anyone else is struggling how to add the view: Just add the empty view as part of your layout, no need to create a view programmatically – Display name Oct 06 '15 at 10:16
  • 6
    A similar implementation from Google Samples: https://github.com/googlesamples/android-XYZTouristAttractions/blob/master/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsRecyclerView.java – jase Feb 23 '16 at 02:50
  • @LalitPoptani Which is wrong, for the same reason than e.g. http://stackoverflow.com/a/28957755/775894 is. – Marc Plano-Lesay Jul 04 '16 at 09:43
  • There is a nice article based on this answer here: http://akbaribrahim.com/empty-view-for-androids-recyclerview/ – ban-geoengineering Dec 02 '16 at 16:03
  • 3
    Cool solution but a class name is strange =) – Denis Sologub Jan 24 '17 at 21:05
  • @Marc Plano-Lesay is there any benefit to this slightly different version of the checkIfEmpty(): private void initEmptyView() { if (mEmptyView != null) { mEmptyView.setVisibility( getAdapter() == null || getAdapter().getItemCount() == 0 ? VISIBLE : GONE); EmptyRecyclerView.this.setVisibility( getAdapter() == null || getAdapter().getItemCount() == 0 ? GONE : VISIBLE); } } found here: http://alexzh.com/tutorials/how-to-setemptyview-to-recyclerview/ – AJW Dec 04 '18 at 00:20
  • 1
    @AJW I guess it's mostly a matter of what you want to achieve. The difference between the two implementations is really minor, and there's none left as soon as an adapter is set. If you don't change the adapter (which is most probably the case), there's no difference. – Marc Plano-Lesay Dec 05 '18 at 04:22
  • @Marc Plano-Lesay I tried to use above in a new project that I converted from RecyclerView with a SQLite database that uses Room, ViewModel and LiveData. EmptyRecyclerView does not work in the new project as the RecyclerView list of items does not load. I am thinking there might be a conflict between the AdapterDataObserver and the MainActivity's Observer that receives the LiveData from the ViewModel/Respository/Dao/RoomDatabase. Any thoughts? – AJW Jul 22 '19 at 04:28
  • I'm not able to extend `EmptyRecyclerView.Adapter` in Kotlin. Any insights? – Arun P M Nov 14 '19 at 07:28
69

With the new data binding feature you can also achieve this in your layout directly:

<TextView
   android:text="No data to display."
   android:visibility="@{dataset.size() > 0 ? View.GONE : View.VISIBLE}" />

In that case you just need to add a variable and an import to the data section of your XML:

<data>
<import type="android.view.View"/>
<variable
    name="dataset"
    type="java.util.List&lt;java.lang.String&gt;"
    />
</data>
André Diermann
  • 2,725
  • 23
  • 28
  • That's great, much better than any other solution here IMHO. Can you export the adapter directly instead of a list, to keep its behavior private? – Marc Plano-Lesay Aug 27 '15 at 08:43
  • 6
    The example above is simplified in order to emphasize the data binding approach. Data binding is very flexible. You could of course import the `Adapter` instead of the dataset and use its `getItemCount()` or wrap everything within a `ViewModel` and set `android:visibility` to `viewModel.getEmptyViewVisibility()`. – André Diermann Aug 27 '15 at 11:28
  • 5
    This should be voted higher, it is an excellent example of the data binding capabilities – Ed Holloway-George Sep 28 '15 at 09:10
  • Thanks you so much for the answer but do you have to make any further change to the Adapter or the layout? I mean, for example, if the dataset changes is the layout update automatically?? Thanks. – javmarina Jan 03 '16 at 17:21
  • 1
    @javmarina No, for me the layout did not continue to update. If my adapter starts with size 0 and then later the dataset grows, the layout will not update as desired. It appears data binding won't work for me. :-( – meisteg Jan 16 '16 at 03:28
  • 4
    Is this going to update even if the adapter is dynamically growing or shrinking to zero? I doubt that. – david Jan 29 '16 at 06:22
  • where to read more about this ? somebody has more examples ?! – Elron Feb 26 '16 at 12:56
  • @elronsmith what kind of examples are you looking for? Is something uncertain in the example above? – André Diermann Feb 29 '16 at 08:24
  • @elronsmith I edited my answer and added a link to Android's data binding guide to avoid confusion. – André Diermann Mar 01 '16 at 15:14
  • 2
    @a11n It doesn't update the layout when list shrinks to 0 or gets data, we have to set value to the binding from the class every time we make any changes to the list, is there any way to update layout by itself ? – Om Infowave Developers Apr 28 '17 at 08:40
26

Solution provided in this link seems perfect. It uses viewType to identify when to show emptyView. No need to create custom RecyclerView

Adding code from the above link:

package com.example.androidsampleproject;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerViewActivity extends Activity {

RecyclerView recyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_recycler_view);
    recyclerView = (RecyclerView) findViewById(R.id.myList);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(new MyAdapter());
}


private class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<String> dataList = new ArrayList<String>();

    public class EmptyViewHolder extends RecyclerView.ViewHolder {
        public EmptyViewHolder(View itemView) {
            super(itemView);
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView data;

        public ViewHolder(View v) {
            super(v);
            data = (TextView) v.findViewById(R.id.data_view);
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size() > 0 ? dataList.size() : 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (dataList.size() == 0) {
            return EMPTY_VIEW;
        }
        return super.getItemViewType(position);
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder vho, final int pos) {
        if (vho instanceof ViewHolder) {
            ViewHolder vh = (ViewHolder) vho;
            String pi = dataList.get(pos);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;

        if (viewType == EMPTY_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
            EmptyViewHolder evh = new EmptyViewHolder(v);
            return evh;
        }

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    private static final int EMPTY_VIEW = 10;
}

}
Sudhasri
  • 1,264
  • 16
  • 19
  • 6
    I think extending RecyclerView is more approtiate solution than this one because generally I have lots of recycler adapters and I want to avoid adding this kind of logic to every one of them. – Gunhan May 31 '15 at 14:31
  • That makes sense @Gunhan, when using many recycler adapters. You may also try to extend a single BaseAdapter customised for common things in all – Sudhasri Jun 02 '15 at 07:06
  • 2
    Even if you have only one adapter and one recycler view, it's not the responsability of the adapter. The adapter is here to present items, not absence of items. – Marc Plano-Lesay Aug 03 '15 at 14:00
  • @Kernald Depends on your use case. Personally I think it is much cleaner the way Sudhasri did it. Especially if you want to show a different view in case of no items being presents like: "No items here, go shopping!" or stuff like that – AgentKnopf Aug 06 '15 at 18:24
  • @Zainodis As you said, it's a different view. It's *not* the adapter's responsability, which is to display items in the recyclerview, nothing else. I agree that technicaly speaking, both solutions work and are pretty much equal. But adapter's items are *not* made to display views like this. – Marc Plano-Lesay Aug 07 '15 at 12:12
  • A dataobserver will consume much more resources than this solution. +1 – Sanket Berde Sep 23 '15 at 18:10
  • I prefer Sudhasri's way. Adapter's responsibility is to show data of RecyclerView. When there is no data, adapter should show _"no data"_ info either. I think it's its responsibility. To make things clearer: imagine you have different view types you want to show, like different numbers of views per row. Actually, this is the case I am working on now. In this case, `getItemViewType` is heavily used. For each situation I use different layout. _"No data"_ is yet another layout. It's clean, light and covers all scenarios and it is right there where it belongs. – Draško Jan 13 '16 at 08:40
  • 2
    @Deveti Putnik: the adapter already indicates that there's no data: item count is equal to zero. When you're using this solution, you're actually failing to give this information, as when the adapter is empty, you still indicate that it contains one element. Which is semantically wrong. View type is supposed to depend on a specific element. Absence of elements is not an element. Using the adapter to handle this case means you're also using the adapter to handle error cases. What has the adapter to do with the fact that you lost the network? Nothing. – Marc Plano-Lesay Feb 05 '16 at 06:42
  • You are probably right, but in the period between posting my previous comment and your reply to it, I implemented three adapters with described behavior and it is working like a charm. – Draško Feb 05 '16 at 07:29
  • @DevetiPutnik like I said earlier, yes, technically speaking, this solution is pretty much equivalent to implementing the logic in the `RecyclerView`. The difference is mainly semantic. But that's the difference between a nice project to maintain and one giving you headaches… – Marc Plano-Lesay Jul 04 '16 at 08:11
10

I would simply prefer a simple solution like,

have your RecyclerView inside a FrameLayout or RelativeLayout with a TextView or other view with showing empty data message with visibility GONE by default and then in the adapter class, apply the logic

Here, I have one TextView with message no data

@Override
public int getItemCount() {
    textViewNoData.setVisibility(data.size() > 0 ? View.GONE : View.VISIBLE);
    return data.size();
}
Lalit Poptani
  • 67,150
  • 23
  • 161
  • 242
3

If you want to support more states such as loading state, error state then you can checkout https://github.com/rockerhieu/rv-adapter-states. Otherwise supporting empty view can be implemented easily using RecyclerViewAdapterWrapper from (https://github.com/rockerhieu/rv-adapter). The main advantage of this approach is you can easily support empty view without changing the logic of the existing adapter:

public class StatesRecyclerViewAdapter extends RecyclerViewAdapterWrapper {
    private final View vEmptyView;

    @IntDef({STATE_NORMAL, STATE_EMPTY})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

    public static final int STATE_NORMAL = 0;
    public static final int STATE_EMPTY = 2;

    public static final int TYPE_EMPTY = 1001;

    @State
    private int state = STATE_NORMAL;

    public StatesRecyclerViewAdapter(@NonNull RecyclerView.Adapter wrapped, @Nullable View emptyView) {
        super(wrapped);
        this.vEmptyView = emptyView;
    }

    @State
    public int getState() {
        return state;
    }

    public void setState(@State int state) {
        this.state = state;
        getWrappedAdapter().notifyDataSetChanged();
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        switch (state) {
            case STATE_EMPTY:
                return 1;
        }
        return super.getItemCount();
    }

    @Override
    public int getItemViewType(int position) {
        switch (state) {
            case STATE_EMPTY:
                return TYPE_EMPTY;
        }
        return super.getItemViewType(position);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_EMPTY:
                return new SimpleViewHolder(vEmptyView);
        }
        return super.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (state) {
            case STATE_EMPTY:
                onBindEmptyViewHolder(holder, position);
                break;
            default:
                super.onBindViewHolder(holder, position);
                break;
        }
    }

    public void onBindEmptyViewHolder(RecyclerView.ViewHolder holder, int position) {
    }

    public static class SimpleViewHolder extends RecyclerView.ViewHolder {
        public SimpleViewHolder(View itemView) {
            super(itemView);
        }
    }
}

Usage:

Adapter adapter = originalAdapter();
StatesRecyclerViewAdapter statesRecyclerViewAdapter = new StatesRecyclerViewAdapter(adapter, emptyView);
rv.setAdapter(endlessRecyclerViewAdapter);

// Change the states of the adapter
statesRecyclerViewAdapter.setState(StatesRecyclerViewAdapter.STATE_EMPTY);
statesRecyclerViewAdapter.setState(StatesRecyclerViewAdapter.STATE_NORMAL);
Hieu Rocker
  • 1,070
  • 12
  • 19
3

Try RVEmptyObserver:

It's an implementation of an AdapterDataObserver that allows you to simply set a View as the default empty layout for your RecylerView. This way, instead of using a custom RecyclerView and making your life harder, you can easily use it with your existing code:


Example Usage:

RVEmptyObserver observer = new RVEmptyObserver(recyclerView, emptyView)
rvAdapter.registerAdapterDataObserver(observer);

You can see the code and example usage in an actual app here.


Class:

public class RVEmptyObserver extends RecyclerView.AdapterDataObserver {
    private View emptyView;
    private RecyclerView recyclerView;

    public RVEmptyObserver(RecyclerView rv, View ev) {
        this.recyclerView = rv;
        this.emptyView    = ev;
        checkIfEmpty();
    }

    private void checkIfEmpty() {
        if (emptyView != null && recyclerView.getAdapter() != null) {
            boolean emptyViewVisible = recyclerView.getAdapter().getItemCount() == 0;
            emptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE);
            recyclerView.setVisibility(emptyViewVisible ? View.GONE : View.VISIBLE);
        }
    }

    public void onChanged() { checkIfEmpty(); }
    public void onItemRangeInserted(int positionStart, int itemCount) { checkIfEmpty(); }
    public void onItemRangeRemoved(int positionStart, int itemCount) { checkIfEmpty(); }
}
Sheharyar
  • 73,588
  • 21
  • 168
  • 215
2

My version, based on https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c

public class EmptyRecyclerView extends RecyclerView {
    @Nullable
    private View emptyView;

    public EmptyRecyclerView(Context context) { super(context); }

    public EmptyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }

    public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private void checkIfEmpty() {
        if (emptyView != null && getAdapter() != null) {
            emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
        }
    }

    private final AdapterDataObserver observer = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkIfEmpty();
        }
    };

    @Override
    public void setAdapter(@Nullable Adapter adapter) {
        final Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(observer);
        }
        checkIfEmpty();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (null != emptyView && (visibility == GONE || visibility == INVISIBLE)) {
            emptyView.setVisibility(GONE);
        } else {
            checkIfEmpty();
        }
    }

    public void setEmptyView(@Nullable View emptyView) {
        this.emptyView = emptyView;
        checkIfEmpty();
    }
}
localhost
  • 5,568
  • 1
  • 33
  • 53
2

I would prefer to implement this functionality in Recycler.Adapter

On your overridden getItemCount method, inject empty check codes there:

@Override
public int getItemCount() {
    if(data.size() == 0) listIsEmtpy();
    return data.size();
}
Bilal
  • 109
  • 1
  • 5
  • 3
    It's not the responsability of the adapter. The adapter is here to present items, not absence of items. – Marc Plano-Lesay Aug 03 '15 at 14:01
  • @Kernald Its our code and its our own way, how we customize it and use it. – Lalit Poptani Jul 03 '16 at 11:29
  • @LalitPoptani sure. But it's a Q&A website, where people are looking for answers, most of the time without thinking more than "what is the copy shortcut again?". Indicating that the solution is semantically wrong (moreover when you also have "rights" solutions) is not really useless… – Marc Plano-Lesay Jul 04 '16 at 08:15
  • @Kernald well I think this solution is the simplest of all and its a good solution as well, because everytime the adapter is notified this will be called and can be used to check the size of the data! – Lalit Poptani Jul 04 '16 at 09:00
  • @LalitPoptani I don't get how modifying the adapter's logic is more simple than a simple drop-in replacement of the `RecyclerView`? – Marc Plano-Lesay Jul 04 '16 at 09:02
  • @kernald did find any other simple solution that this one? – Lalit Poptani Jul 04 '16 at 09:06
  • @LalitPoptani http://stackoverflow.com/a/27801394/775894 is a simple drop-in, you integrate the class to your project, then use it instead of `RecyclerView`, without having to think about this when writing your adapters. It looks a lot more simple to me. – Marc Plano-Lesay Jul 04 '16 at 09:09
  • @Kernald why should I include a whole bunch of code, of that class instead a single line of code doing good work for me? – Lalit Poptani Jul 04 '16 at 09:11
  • @LalitPoptani It's not a single line of code. You have to implement `listIsEmpty()`, which would do nearly the same as the class I linked above. And decouple this responsibility from your adapter, which is not meant to do this. – Marc Plano-Lesay Jul 04 '16 at 09:13
  • @kernald here is my answer, with one line of code - http://stackoverflow.com/a/38181271/726863 – Lalit Poptani Jul 04 '16 at 09:31
  • Ruining modularity just to prove a point? Really? Let's agree to disagree then. – Marc Plano-Lesay Jul 04 '16 at 09:34
  • @Kernald, I don't mind and care if you disagree, I just pointed that you pointed someones answer to be wrong because you have a lengthy answer which might be correct and that guy has a answer with less code and is right :D :P – Lalit Poptani Jul 04 '16 at 09:40
  • @Kernald and last but not the least, get we soon my son :D – Lalit Poptani Jul 04 '16 at 09:53
  • 2
    @MarcPlano-Lesay is correct. This answer is incomplete because it does not handle the case when the empty view needs to be invisible once the items get filled. After implementing that part this solution becomes inefficient because every time the adapter queries the item count, `setVisibility()` gets called. Sure you could add some flags to compensate but that is when it gets more complex. – razzledazzle Oct 15 '16 at 11:58
2

I have fixed this:
Created layout layout_recyclerview_with_emptytext.xml file.
Created EmptyViewRecyclerView.java
---------

EmptyViewRecyclerView emptyRecyclerView = (EmptyViewRecyclerView) findViewById(R.id.emptyRecyclerViewLayout);
emptyRecyclerView.addAdapter(mPrayerCollectionRecyclerViewAdapter, "There is no prayer for selected category.");

layout_recyclerview_with_emptytext.xml file

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/switcher"
>

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

<com.ninestars.views.CustomFontTextView android:id="@+id/recyclerViewEmptyTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="Empty Text"
    android:layout_gravity="center"
    android:gravity="center"
    android:textStyle="bold"
    />

    </merge>


EmptyViewRecyclerView.java

public class EmptyViewRecyclerView extends ViewSwitcher {
private RecyclerView mRecyclerView;
private CustomFontTextView mRecyclerViewExptyTextView;

public EmptyViewRecyclerView(Context context) {
    super(context);
    initView(context);
}

public EmptyViewRecyclerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
}


private void initView(Context context) {
    LayoutInflater.from(context).inflate(R.layout.layout_recyclerview_with_emptytext, this, true);
    mRecyclerViewExptyTextView = (CustomFontTextView) findViewById(R.id.recyclerViewEmptyTextView);
    mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
}

public void addAdapter(final RecyclerView.Adapter<?> adapter) {
    mRecyclerView.setAdapter(adapter);
    adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            if(adapter.getItemCount() > 0) {
                if (R.id.recyclerView == getNextView().getId()) {
                    showNext();
                }
            } else {
                if (R.id.recyclerViewEmptyTextView == getNextView().getId()) {
                    showNext();
                }
            }
        }
    });
}

public void addAdapter(final RecyclerView.Adapter<?> adapter, String emptyTextMsg) {
    addAdapter(adapter);
    setEmptyText(emptyTextMsg);
}

public RecyclerView getRecyclerView() {
    return mRecyclerView;
}

public void setEmptyText(String emptyTextMsg) {
    mRecyclerViewExptyTextView.setText(emptyTextMsg);
}

}
Ashwani
  • 31
  • 3
1
public class EmptyRecyclerView extends RecyclerView {
  @Nullable View emptyView;

  public EmptyRecyclerView(Context context) { super(context); }

  public EmptyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }

  public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  void checkIfEmpty() {
    if (emptyView != null) {
      emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
    }
  }

  final @NotNull AdapterDataObserver observer = new AdapterDataObserver() {
    @Override public void onChanged() {
      super.onChanged();
      checkIfEmpty();
    }
  };

  @Override public void setAdapter(@Nullable Adapter adapter) {
    final Adapter oldAdapter = getAdapter();
    if (oldAdapter != null) {
      oldAdapter.unregisterAdapterDataObserver(observer);
    }
    super.setAdapter(adapter);
    if (adapter != null) {
      adapter.registerAdapterDataObserver(observer);
    }
  }

  public void setEmptyView(@Nullable View emptyView) {
    this.emptyView = emptyView;
    checkIfEmpty();
  }
}

something like this might help

  • 2
    This is incomplete. You'll probably need to hide the `RecyclerView` when the `emptyView` is visible (and the opposite). You'll also need to call `checkIfEmpty()` on `onItemRangeInserted()` and `onItemRangeRemoved()`. Oh, and you could have cited your source: https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c – Marc Plano-Lesay Jan 06 '15 at 15:02
1

I think this is more complete with both ErrorView & EmptyView https://gist.github.com/henrytao-me/2f7f113fb5f2a59987e7

Henry Tao
  • 1,104
  • 10
  • 14
1

You can just paint the text on the RecyclerView when it's empty. The following custom subclass supports empty, failed, loading, and offline modes. For successful compilation add recyclerView_stateText color to your resources.

/**
 * {@code RecyclerView} that supports loading and empty states.
 */
public final class SupportRecyclerView extends RecyclerView
{
    public enum State
    {
        NORMAL,
        LOADING,
        EMPTY,
        FAILED,
        OFFLINE
    }

    public SupportRecyclerView(@NonNull Context context)
    {
        super(context);

        setUp(context);
    }

    public SupportRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs)
    {
        super(context, attrs);

        setUp(context);
    }

    public SupportRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);

        setUp(context);
    }

    private Paint textPaint;
    private Rect textBounds;
    private PointF textOrigin;

    private void setUp(Context c)
    {
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(ContextCompat.getColor(c, R.color.recyclerView_stateText));

        textBounds = new Rect();
        textOrigin = new PointF();
    }

    private State state;

    public State state()
    {
        return state;
    }

    public void setState(State newState)
    {
        state = newState;
        calculateLayout(getWidth(), getHeight());
        invalidate();
    }

    private String loadingText = "Loading...";

    public void setLoadingText(@StringRes int resId)
    {
        loadingText = getResources().getString(resId);
    }

    private String emptyText = "Empty";

    public void setEmptyText(@StringRes int resId)
    {
        emptyText = getResources().getString(resId);
    }

    private String failedText = "Failed";

    public void setFailedText(@StringRes int resId)
    {
        failedText = getResources().getString(resId);
    }

    private String offlineText = "Offline";

    public void setOfflineText(@StringRes int resId)
    {
        offlineText = getResources().getString(resId);
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        String s = stringForCurrentState();
        if (s == null)
            return;

        canvas.drawText(s, textOrigin.x, textOrigin.y, textPaint);
    }

    private void calculateLayout(int w, int h)
    {
        String s = stringForCurrentState();
        if (s == null)
            return;

        textPaint.setTextSize(.1f * w);
        textPaint.getTextBounds(s, 0, s.length(), textBounds);

        textOrigin.set(
         w / 2f - textBounds.width() / 2f - textBounds.left,
         h / 2f - textBounds.height() / 2f - textBounds.top);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);

        calculateLayout(w, h);
    }

    private String stringForCurrentState()
    {
        if (state == State.EMPTY)
            return emptyText;
        else if (state == State.LOADING)
            return loadingText;
        else if (state == State.FAILED)
            return failedText;
        else if (state == State.OFFLINE)
            return offlineText;
        else
            return null;
    }
}
Aleks N.
  • 6,051
  • 3
  • 42
  • 42
1

From my point of view the easiest way how to do an empty View is to create new empty RecyclerView with layout you want to inflate as a background. And this empty Adapter is set when you check your dataset size.

0

A more easy to use, and more resource save way to set Empty Label of RecyclerView.

In short, a new View named RecyclerViewEmpty is introduced. In its onDraw method, if the adapter is empty, it just paint an Empty Label on its center, otherwise, continue super.onDraw();

class RecyclerViewEmpty extends RecyclerView {
    ....
    @Override
    public void onDraw(Canvas canvas) {
        Adapter a = this.getAdapter();
        if(a==null || a.getItemCount()<1) {
            int x= (this.getWidth()-strWidth)>>1;
            int y = this.getHeight()>>1 ;
            canvas.drawText(this.emptyLabel, x, y, labelPaint);
        }
        else {
            super.onDraw(canvas);
        }
    }
    ....
}

For details, please refer to code: https://github.com/stzdzyhs/recyclerview-demo

desertnaut
  • 57,590
  • 26
  • 140
  • 166
chenzero
  • 57
  • 1
  • 8
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/28857548) – EJoshuaS - Stand with Ukraine Apr 28 '21 at 15:25
  • oops, the code idea is almost same with @Aleks N. answer... – chenzero Apr 28 '21 at 16:32