Equivalent of ListView.setEmptyView in RecyclerView

AndroidAndroid Recyclerview

Android Problem Overview


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

Android Solutions


Solution 1 - Android

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();
}




}

}

Solution 2 - Android

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>

Solution 3 - Android

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;
}

}

Solution 4 - Android

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();
}

Solution 5 - Android

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);

Solution 6 - Android

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(); }
}

Solution 7 - Android

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();
    }
}

Solution 8 - Android

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();
}

Solution 9 - Android

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);
}

}

Solution 10 - Android

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

Solution 11 - Android

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

Solution 12 - Android

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;
	}
}

Solution 13 - Android

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.

Solution 14 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManish MulimaniView Question on Stackoverflow
Solution 1 - AndroidMarc Plano-LesayView Answer on Stackoverflow
Solution 2 - AndroidAndré DiermannView Answer on Stackoverflow
Solution 3 - AndroidSudhasriView Answer on Stackoverflow
Solution 4 - AndroidLalit PoptaniView Answer on Stackoverflow
Solution 5 - AndroidHieu RockerView Answer on Stackoverflow
Solution 6 - AndroidSheharyarView Answer on Stackoverflow
Solution 7 - AndroidlocalhostView Answer on Stackoverflow
Solution 8 - AndroidBilalView Answer on Stackoverflow
Solution 9 - AndroidAshwaniView Answer on Stackoverflow
Solution 10 - AndroidMunawwar Hussain SheliaView Answer on Stackoverflow
Solution 11 - AndroidHenry TaoView Answer on Stackoverflow
Solution 12 - AndroidAleks N.View Answer on Stackoverflow
Solution 13 - Androiduser7108272View Answer on Stackoverflow
Solution 14 - AndroidchenzeroView Answer on Stackoverflow