308

I am used to put an special view inside the layout file as described in the ListActivity documentation to be displayed when there is no data. This view has the id "android:id/empty".

<TextView
    android:id="@android:id/empty"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/no_data" />

I wonder how this can be done with the new RecyclerView?

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
JJD
  • 50,076
  • 60
  • 203
  • 339
  • 2
    You can use https://github.com/rockerhieu/rv-adapter-states, it supports not only empty view but also loading view and error view. And you can use it without changing the logic of the existing adapter. – Hieu Rocker Aug 11 '15 at 17:42
  • 36
    It is nothing but fascinating that there doesn't seem to be a simple `setEmptyView()` method against a `RecyclerView`... – Subby May 11 '16 at 15:45
  • Here is my answer , please check this link https://stackoverflow.com/a/58411351/5449220 – Rakesh Verma Oct 16 '19 at 10:49
  • @Subby, agree, but `setEmptyView()` also had disadvantage. When loading data, we didn't want to show empty view in `ListView`, but it did. So, we had to implement some logic. Currently I use https://stackoverflow.com/a/48049142/2914140. – CoolMind Nov 15 '19 at 11:35

15 Answers15

346

On the same layout where is defined the RecyclerView, add the TextView:

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

<TextView
    android:id="@+id/empty_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:visibility="gone"
    android:text="@string/no_data_available" />

At the onCreate or the appropriate callback you check if the dataset that feeds your RecyclerView is empty. If the dataset is empty, the RecyclerView is empty too. In that case, the message appears on the screen. If not, change its visibility:

private RecyclerView recyclerView;
private TextView emptyView;

// ...

recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
emptyView = (TextView) rootView.findViewById(R.id.empty_view);

// ...

if (dataset.isEmpty()) {
    recyclerView.setVisibility(View.GONE);
    emptyView.setVisibility(View.VISIBLE);
}
else {
    recyclerView.setVisibility(View.VISIBLE);
    emptyView.setVisibility(View.GONE);
}
JJD
  • 50,076
  • 60
  • 203
  • 339
slellis
  • 3,693
  • 1
  • 12
  • 11
  • 15
    That does not work for me because the recycler view layout is inflated in the fragment whereas the decision whether to show an normal item layout or an no-items layout is made in the adapter. – JJD Feb 06 '15 at 12:00
  • 2
    @JJD when using a Fragment, the same thing applies. When you inflate your fragment view you have the recyclerview and then another layout that would be used for your empty view. Usually in my fragments I will have the follow there views: 1) recyclerview, 2) empty view, and 3) progressbar view. I usually pass the adapter whatever the list is, but based on the size of the list i will either hide the recyclerview and show the empty or vice versa. If a recyclerview has an empty list, then it does not do anything with it. You just see an empty view. – Ray Hunter Apr 12 '15 at 16:11
  • what if the content of recyclerview can be decreased, how to detect it if data changed? – stackex Apr 29 '15 at 02:18
  • 1
    @stackex The verification must be in the onCreateView callback of the Fragment or onResume of Activity. In this way, the verification will be made immediately before the screen be displayed to the user and will work for both, increasing or decreasing the amount of rows in the dataset. – slellis Apr 29 '15 at 13:35
  • Is there no listener that one can attach tot he adapter to check if there is any items int eh adapter? – Zapnologica Oct 13 '15 at 11:58
  • 2
    @Zapnologica You can do it the other way around: Use an Eventbus (such as greenrobots Eventbus or Otto), then have the Adapter post on the eventbus when the dataset changes. In the fragment all you do is create a method whose parameters correspond to what the Adapter is passing into the Eventbus.post method and then change the layout accordingly. A more simple but also more coupled approach is to create a callback interface in the adapter, and upon creating the adapter have the fragment implement it. – AgentKnopf Dec 03 '15 at 13:52
  • 2
    In the layout I get multiple root tags. What does your full layout file look like? – powder366 Feb 07 '16 at 12:43
  • @slellis your answer was great (Y),but i want to display empty view without using Textview is it possible ? – Arbaz.in Aug 23 '18 at 04:24
  • This solution works for me perfectly, I only use the Kotlin, so check the condition like: override fun getItemCount() = dataSet.size.also { showEmpty(dataSet.isEmpty()) } – user2305886 Dec 12 '20 at 00:52
203

For my projects I made this solution (RecyclerView with setEmptyView method):

public class RecyclerViewEmptySupport extends RecyclerView {
    private View emptyView;

    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {


        @Override
        public void onChanged() {
            Adapter<?> adapter =  getAdapter();
            if(adapter != null && emptyView != null) {
                if(adapter.getItemCount() == 0) {
                    emptyView.setVisibility(View.VISIBLE);
                    RecyclerViewEmptySupport.this.setVisibility(View.GONE);
                }
                else {
                    emptyView.setVisibility(View.GONE);
                    RecyclerViewEmptySupport.this.setVisibility(View.VISIBLE);
                }
            }

        }
    };

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

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

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

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);

        if(adapter != null) {
            adapter.registerAdapterDataObserver(emptyObserver);
        }

        emptyObserver.onChanged();
    }

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

And you should use it instead of RecyclerView class:

<com.maff.utils.RecyclerViewEmptySupport android:id="@+id/list1"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    />

<TextView android:id="@+id/list_empty"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Empty"
    />

and

RecyclerViewEmptySupport list = 
    (RecyclerViewEmptySupport)rootView.findViewById(R.id.list1);
list.setLayoutManager(new LinearLayoutManager(context));
list.setEmptyView(rootView.findViewById(R.id.list_empty));
JJD
  • 50,076
  • 60
  • 203
  • 339
maff91
  • 2,197
  • 1
  • 10
  • 6
  • 3
    Did you take this code from [this answer](http://stackoverflow.com/a/27801394/1276636) or [from here](https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c)? – Sufian Jul 27 '15 at 09:31
  • @Sufian No, I didn't see that answer, just found the same way. But that code definitely looks prettier than mine:) – maff91 Jul 30 '15 at 12:43
  • 2
    I tried to combine this with the FirebaseRecyclerAdapter, it didnt work. This is because the FirebaseRecyclerAdapter does not call the onChange (of the AdapterDataObserver), instead it calls the onItem*-methods. To make it work add: ` Override public void onItemRangeRemoved(int positionStart, int itemCount) { //the check } Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { //the check } //etc.. ` – FrankkieNL Jun 12 '16 at 15:06
  • 1
    Why do you check if the adapter is null in the observer? if the adapter is null the observer should never be called. – Stimsoni Jul 27 '16 at 01:46
  • Might as well become a lib. – Mauker Feb 10 '17 at 20:39
  • 27
    ugh, I must say this is just classic Google BS. I have to implement this just to add an empty view? It should be included in their cr@psh1t SDK! For godsake! – Neon Warge Apr 05 '17 at 01:59
  • Never unregister? when rotate devices? – Codelaby Oct 03 '19 at 10:38
  • This will not work if you use DiffUtil to dispatch changes. – Homayoon Ahmadi May 29 '22 at 09:24
73

Here is a solution using only a custom adapter with a different view type for the empty situation.

public class EventAdapter extends 
    RecyclerView.Adapter<EventAdapter.ViewHolder> {

    private static final int VIEW_TYPE_EVENT = 0;
    private static final int VIEW_TYPE_DATE = 1;
    private static final int VIEW_TYPE_EMPTY = 2;

    private ArrayList items;

    public EventAdapter(ArrayList items) {
        this.items = items;
    }

    @Override
    public int getItemCount() {
        if(items.size() == 0){
            return 1;
        }else {
            return items.size();
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (items.size() == 0) {
            return VIEW_TYPE_EMPTY;
        }else{
            Object item = items.get(position);
            if (item instanceof Event) {
                return VIEW_TYPE_EVENT;
            } else {
                return VIEW_TYPE_DATE;
            }
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;
        ViewHolder vh;
        if (viewType == VIEW_TYPE_EVENT) {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event, parent, false);
            vh = new ViewHolderEvent(v);
        } else if (viewType == VIEW_TYPE_DATE) {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event_date, parent, false);
            vh = new ViewHolderDate(v);
        } else {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event_empty, parent, false);
            vh = new ViewHolder(v);
        }

        return vh;
    }

    @Override
    public void onBindViewHolder(EventAdapter.ViewHolder viewHolder, 
                                 final int position) {
        int viewType = getItemViewType(position);
        if (viewType == VIEW_TYPE_EVENT) {
            //...
        } else if (viewType == VIEW_TYPE_DATE) {
            //...
        } else if (viewType == VIEW_TYPE_EMPTY) {
            //...
        }
    }

    public static class ViewHolder extends ParentViewHolder {
        public ViewHolder(View v) {
            super(v);
        }
    }

    public static class ViewHolderDate extends ViewHolder {
        public ViewHolderDate(View v) {
            super(v);
        }
    }

    public static class ViewHolderEvent extends ViewHolder {
        public ViewHolderEvent(View v) {
            super(v);
        }
    }

}
radu122
  • 2,865
  • 24
  • 24
  • 3
    @sfk92fksdf Why is it error prone? This is the way you're supposed to handle multiple view types in recyclerview – MSpeed Apr 15 '17 at 14:58
  • @billynomates, because `getItemCount()` should be a oneliner but here we have a non-trivial `if` with some hidden logic. This logic also appears awkwardly in `getItemViewType` and god knows where else – Alexey Andronov Apr 16 '17 at 04:21
  • 5
    It doesn't need to be one line. If you've got multiple view types, this is the way to do it. – MSpeed Apr 17 '17 at 09:24
  • @sfk92fksdf What do you mean by hidden logic? The code is right up there ▲ – radu122 May 17 '17 at 12:09
  • @radu122 by "hidden" I mean non-trivial logic and why it's evil I wrote in my previous comment :) – Alexey Andronov May 18 '17 at 02:40
  • I was thinking same :) #great – iamnaran May 16 '19 at 06:17
  • 1
    in my opinion this answer should be the accepted answer. It really is the way to handle mutliple view types – Ivo Sep 12 '19 at 08:48
  • Worth noting is that in `onBindViewHolder()` you will have to cast `viewHolder` to specific holder so you can actually access views inside them. Like `ViewHolderDate date = (ViewHolderDate) viewHandler;`. See this comment: https://stackoverflow.com/questions/26245139/how-to-create-recyclerview-with-multiple-view-type#comment41176610_26245463 – jean d'arme Feb 23 '20 at 13:26
  • 1
    perfect! Thanks! The point is an empty adapter won't work! it won't call on its onBindViewHolder(), but with setting the item count to 1 it works! – Araz May 04 '22 at 11:30
57

I use ViewSwitcher

<ViewSwitcher
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/switcher"
    >

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

    <TextView android:id="@+id/text_empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/list_empty"
        android:gravity="center"
        />

</ViewSwitcher>

in code you will check cursor/dataset and switch views.

void showItems(Cursor items) {
    if (items.size() > 0) {

        mAdapter.switchCursor(items);

        if (R.id.list == mListSwitcher.getNextView().getId()) {
            mListSwitcher.showNext();
        }
    } else if (R.id.text_empty == mListSwitcher.getNextView().getId()) {
        mListSwitcher.showNext();
    }
}

Also you can set animations if you wish with a couple lines of code

mListSwitcher.setInAnimation(slide_in_left);
mListSwitcher.setOutAnimation(slide_out_right);
wnc_21
  • 1,751
  • 13
  • 20
50

Since Kevin's answer is not complete.
This is more correct answer if you use RecyclerAdapter's notifyItemInserted and notifyItemRemoved to update dataset. See the Kotlin version another user added below.

Java:

mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {

    @Override
    public void onChanged() {
        super.onChanged();
        checkEmpty();
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        super.onItemRangeInserted(positionStart, itemCount);
        checkEmpty();
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        super.onItemRangeRemoved(positionStart, itemCount);
        checkEmpty();
    }

    void checkEmpty() {
        mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
    }
});

Kotlin

adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
    override fun onChanged() {
        super.onChanged()
        checkEmpty()
    }

    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
        super.onItemRangeInserted(positionStart, itemCount)
        checkEmpty()
    }

    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
        super.onItemRangeRemoved(positionStart, itemCount)
        checkEmpty()
    }

    fun checkEmpty() {
        empty_view.visibility = (if (adapter.itemCount == 0) View.VISIBLE else View.GONE)
    }
})
jungledev
  • 4,195
  • 1
  • 37
  • 52
wonsuc
  • 3,498
  • 1
  • 27
  • 30
22

RVEmptyObserver

Instead of using a custom RecyclerView, extending an AdapterDataObserver is a simpler solution that allows setting a custom View that is displayed when there are no items in the list:

Example Usage:

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

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
10

On your adapter's getItemViewType check if the adapter has 0 elements and return a different viewType if so.

Then on your onCreateViewHolder check if the viewType is the one you returned earlier and inflate a diferent view. In this case a layout file with that TextView

EDIT

If this is still not working then you might want to set the size of the view programatically like this:

Point size = new Point();
((WindowManager)itemView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size);

And then when you inflate your view call:

inflatedView.getLayoutParams().height = size.y;
inflatedView.getLayoutParams().width = size.x;
Pedro Oliveira
  • 20,442
  • 8
  • 55
  • 82
  • Sounds good. However I do not get into `onCreateViewHolder` when my dataset is `null` or empty since I have to return `0` in `getItemCount`. – JJD Feb 05 '15 at 16:31
  • Then return 1 if it is null :) – Pedro Oliveira Feb 05 '15 at 16:50
  • Works somewhat. Thank you. - Minor detail is that I cannot get the empty list item being **centered vertically** in the recycler view. The text view stays at the top of the screen nonetheless I set `android:layout_gravity="center"` and `android:layout_height="match_parent"` for the parent recycler view. – JJD Feb 06 '15 at 12:04
  • It depends on the layout of the row you're using. It should match_parent on it's height. If you can't still make it work, add a global layout listener and set it's layout parameters to have the same height and width as the screen. – Pedro Oliveira Feb 06 '15 at 12:05
  • All containers (activity, fragment, linear layout, recycler view, text view) are set to `android:layout_height="match_parent"`. Though the row does not expand to the screen height. – JJD Feb 06 '15 at 12:10
  • Remember to pass the parent to the inflated row. How are you inflating your row? Don't pass null as a parent. Pass the recyclerview and false as the 3rd parameter – Pedro Oliveira Feb 06 '15 at 12:12
  • I pass the `parent` which comes as parameter from `onCreateViewHolder(ViewGroup parent, int viewType)` to `layoutInflater.inflate(layoutResourceId, parent, false)` in the adapter. – JJD Feb 06 '15 at 12:15
  • You might need to change the layoutparams of the view to have the same width and height of the whole screen. I will change the answer with that info. – Pedro Oliveira Feb 06 '15 at 13:03
  • `Display.getSize()` has the disadvantage that it returns the [absolute screen dimension](http://developer.android.com/reference/android/view/Display.html#getSize%28android.graphics.Point%29) without consider screen decorations such as the action bar. I am not sure what they mean by "window size". – JJD Feb 06 '15 at 13:38
  • Then I think you can just grab the height and width of the recyclerview. You will need to add a globalistener into the empty row – Pedro Oliveira Feb 06 '15 at 14:02
  • Do you mean a `OnLayoutChangeListener`? – JJD Feb 06 '15 at 14:28
  • `OnGlobalLayoutListener` – Pedro Oliveira Feb 06 '15 at 14:29
  • I decided to switch to @slellis approach. I retrieve the data in the fragment in `onViewCreated` and pass it to the adapter. There is the right moment to check the result set and toggle the visibility of the recycler view and the "empty" view. - Thank you anyways - I learned a lot! – JJD Feb 06 '15 at 14:49
9

Here is my class for show empty view, retry view (when load api failed) and loading progress for RecyclerView

public class RecyclerViewEmptyRetryGroup extends RelativeLayout {
    private RecyclerView mRecyclerView;
    private LinearLayout mEmptyView;
    private LinearLayout mRetryView;
    private ProgressBar mProgressBar;
    private OnRetryClick mOnRetryClick;

    public RecyclerViewEmptyRetryGroup(Context context) {
        this(context, null);
    }

    public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onViewAdded(View child) {
        super.onViewAdded(child);
        if (child.getId() == R.id.recyclerView) {
            mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
            return;
        }
        if (child.getId() == R.id.layout_empty) {
            mEmptyView = (LinearLayout) findViewById(R.id.layout_empty);
            return;
        }
        if (child.getId() == R.id.layout_retry) {
            mRetryView = (LinearLayout) findViewById(R.id.layout_retry);
            mRetryView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mRetryView.setVisibility(View.GONE);
                    mOnRetryClick.onRetry();
                }
            });
            return;
        }
        if (child.getId() == R.id.progress_bar) {
            mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        }
    }

    public void loading() {
        mRetryView.setVisibility(View.GONE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.VISIBLE);
    }

    public void empty() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRetryView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public void retry() {
        mRetryView.setVisibility(View.VISIBLE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public void success() {
        mRetryView.setVisibility(View.GONE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    public void setOnRetryClick(OnRetryClick onRetryClick) {
        mOnRetryClick = onRetryClick;
    }

    public interface OnRetryClick {
        void onRetry();
    }
}

activity_xml

<...RecyclerViewEmptyRetryGroup
        android:id="@+id/recyclerViewEmptyRetryGroup">

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

        <LinearLayout
            android:id="@+id/layout_empty">
            ...
        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout_retry">
            ...
        </LinearLayout>

        <ProgressBar
            android:id="@+id/progress_bar"/>

</...RecyclerViewEmptyRetryGroup>

enter image description here

The source is here https://github.com/PhanVanLinh/AndroidRecyclerViewWithLoadingEmptyAndRetry

Linh
  • 57,942
  • 23
  • 262
  • 279
9

Use AdapterDataObserver in custom RecyclerView

Kotlin:

RecyclerViewEnum.kt

enum class RecyclerViewEnum {
    LOADING,
    NORMAL,
    EMPTY_STATE
}

RecyclerViewEmptyLoadingSupport.kt

class RecyclerViewEmptyLoadingSupport : RecyclerView {

    var stateView: RecyclerViewEnum? = RecyclerViewEnum.LOADING
        set(value) {
            field = value
            setState()
        }
    var emptyStateView: View? = null
    var loadingStateView: View? = null


    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}


    private val dataObserver = object : AdapterDataObserver() {
        override fun onChanged() {
            onChangeState()
        }

        override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
            super.onItemRangeRemoved(positionStart, itemCount)
            onChangeState()
        }

        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            super.onItemRangeInserted(positionStart, itemCount)
            onChangeState()
        }
    }


    override fun setAdapter(adapter: RecyclerView.Adapter<*>?) {
        super.setAdapter(adapter)
        adapter?.registerAdapterDataObserver(dataObserver)
        dataObserver.onChanged()
    }


    fun onChangeState() {
        if (adapter?.itemCount == 0) {
            emptyStateView?.visibility = View.VISIBLE
            loadingStateView?.visibility = View.GONE
            this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
        } else {
            emptyStateView?.visibility = View.GONE
            loadingStateView?.visibility = View.GONE
            this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
        }
    }

    private fun setState() {

        when (this.stateView) {
            RecyclerViewEnum.LOADING -> {
                loadingStateView?.visibility = View.VISIBLE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
                emptyStateView?.visibility = View.GONE
            }

            RecyclerViewEnum.NORMAL -> {
                loadingStateView?.visibility = View.GONE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
                emptyStateView?.visibility = View.GONE
            }
            RecyclerViewEnum.EMPTY_STATE -> {
                loadingStateView?.visibility = View.GONE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
                emptyStateView?.visibility = View.VISIBLE
            }
        }
    }
}

layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/emptyLabelTv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="empty" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/loadingView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:gravity="center"
        android:orientation="vertical">

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_gravity="center"
            android:indeterminate="true"
            android:theme="@style/progressBarBlue" />
    </LinearLayout>

    <com.peeyade.components.recyclerView.RecyclerViewEmptyLoadingSupport
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

in activity use this way:

recyclerView?.apply {
        layoutManager = GridLayoutManager(context, 2)
        emptyStateView = emptyView
        loadingStateView = loadingView
        adapter = adapterGrid
    }

    // you can set LoadingView or emptyView manual
    recyclerView.stateView = RecyclerViewEnum.EMPTY_STATE
    recyclerView.stateView = RecyclerViewEnum.LOADING
Rasoul Miri
  • 11,234
  • 1
  • 68
  • 78
  • 2
    If you go with that solution pay attention that using enums is insufficient due to its increased memory allocation size. Use Integer or String depending on your context with appropriate `StringDef` or `IntDef` annotations – Andrii Artamonov Nov 01 '19 at 10:56
  • @AndriiArtamonov no it doesn't matter. Use enums - https://stackoverflow.com/a/54893141/2371425 – Sakiboy Mar 23 '21 at 04:04
7

One more way is to use addOnChildAttachStateChangeListener which handles appearing/disappearing child views in RecyclerView.

recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {
                forEmptyTextView.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                forEmptyTextView.setVisibility(View.VISIBLE);
            }
        });
hidd
  • 336
  • 3
  • 11
  • This should be the correct answer. To add, inside the two methods you can use `recyclerView.getAdapter().getItemCount()` to decide. – Yusuph wickama Jul 18 '22 at 13:04
2

I added RecyclerView and alternative ImageView to the RelativeLayout:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/no_active_jobs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_active_jobs" />

    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

and then in Adapter:

@Override
public int getItemCount() {
    if (mOrders.size() == 0) {
        mRecyclerView.setVisibility(View.INVISIBLE);
    } else {
        mRecyclerView.setVisibility(View.VISIBLE);
    }
    return mOrders.size();
}
YTerle
  • 2,606
  • 6
  • 24
  • 40
  • 7
    how to reach `mRecyclerView` inside the `Adapter` – Basheer AL-MOMANI May 06 '16 at 14:52
  • 2
    It's not good design pattern to tie Adapter to specific Activity. It's better to do that via interfaces – ruX Jul 27 '16 at 15:13
  • 1
    Also this is causing a layout overdraw, which is not recommended and it could cause some performance issues. See https://www.youtube.com/watch?v=T52v50r-JfE for more details – Felipe Conde Sep 13 '16 at 15:33
1

The simplest solution is to use RecyclerView.AdapterDataObserver and register it in your recyclerview after your adapter is initialized.

val emptyDataObserver = EmptyDataObserver(recycler_view, empty_data_parent)
yourAdapter.registerAdapterDataObserver(emptyDataObserver)

Where, recycler_view & empty_data_parent are layouts in your activity, constraint those views as you want and make its visibility GONE. Then create your own empty dataset view with image & text.

 <include
            android:id="@+id/empty_data_parent"
            layout="@layout/item_empty_dataset"
            android:layout_width="match_parent"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="parent"
            android:layout_height="match_parent"
            android:layout_gravity="center" />

Here's an example of empty_data_set_view.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_gravity="center"
    android:gravity="center"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    tools:ignore="RtlHardcoded">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.422"
        android:src="@drawable/ic_empty_dataset_1" />

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Title is called when title is placed"
        android:padding="10dp"
        android:fontFamily="@font/normal"
        android:textStyle="bold"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView2" />

    <TextView
        android:id="@+id/sub_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text=" Subtitle is called when title is placed. Subtitle is called when title is placed"
        android:padding="5dp"
        android:fontFamily="@font/normal"
        android:gravity="center"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/title" />
</androidx.constraintlayout.widget.ConstraintLayout>

And This EmptyDataObserver class that will do the work.

import android.view.View
import androidx.recyclerview.widget.RecyclerView


class EmptyDataObserver constructor(rv: RecyclerView?, ev: View?): RecyclerView.AdapterDataObserver() {

    private var emptyView: View? = null
    private var recyclerView: RecyclerView? = null

    init {
        recyclerView = rv
        emptyView = ev
        checkIfEmpty()
    }


    private fun checkIfEmpty() {
        if (emptyView != null && recyclerView!!.adapter != null) {
            val emptyViewVisible = recyclerView!!.adapter!!.itemCount == 0
            emptyView!!.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE
            recyclerView!!.visibility = if (emptyViewVisible) View.GONE else View.VISIBLE
        }
    }

    override fun onChanged() {
        super.onChanged()
        checkIfEmpty()
    }

    override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
        super.onItemRangeChanged(positionStart, itemCount)
    }

}
iamnaran
  • 1,894
  • 2
  • 15
  • 24
0

Just incase you are working with a FirebaseRecyclerAdapter this post works as a charm https://stackoverflow.com/a/39058636/6507009

knightcube
  • 139
  • 1
  • 13
  • 7
    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. – Anh Pham Jul 21 '17 at 14:49
0

if you want to display a text view when the recycler view is empty you can do it like this :

ArrayList<SomeDataModel> arrayList = new ArrayList<>();

RecycleAdapter recycleAdapter =  new RecycleAdapter(getContext(),project_Ideas);

recyclerView..setAdapter(recycleAdapter);

if(arrayList.isEmpty())
{
    emptyTextView.setVisibility(View.VISIBLE);
    recyclerView.setVisibility(View.GONE);
}

I Assume you have TextView

and XML like this

 android:visibility="gone"

    
  
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Fitsum Alemu
  • 359
  • 3
  • 2
0

This how to show empty view while filtering and updating RecyclerView using LiveData

    ViewModel.getInstance().getCustomers?.observe(viewLifecycleOwner, {customerList ->
           //assign your adapter with your list then
        listAdapter?.notifyDataSetChanged()
        isListItemEmpty(customerList?.isEmpty())
}

Create an interface to communicate between adapter and fragment and register it inside adapter

 interface EmptyListener {
            fun isListEmpty(isEmpty: Boolean)
        }

Then call it inside publishResults:

        dataList?.isEmpty()?.let { mListener?.isListEmpty(it) }

Finally inside Fragment implement your interface:

 override fun isListEmpty(isEmpty: Boolean) {
        if (isEmpty) {
            your_list?.visibility = View.GONE
            empty_view?.visibility = View.VISIBLE
        } else {
            empty_view?.visibility = View.GONE
            your_list?.visibility = View.VISIBLE
        }
    }