RecyclerView

How to set the empty view to RecyclerView

Alex Zhukovich 5 min read
How to set the empty view to RecyclerView
Table of Contents

Almost all applications display information in the lists. We have two basic components for displaying list of data in Android. It’s a ListView and a RecyclerView. The RecyclerView is more efficient component from an Android documentation.

After first run, some applications has an empty list of data, as example ToDo list. Of course, it’s really bad user experience to show empty list. Ideally, we should display some message which explains current situation and/or how to add a data.

When we come back to ListView, this component has a simple method which allows display a view when list is empty. It is called setEmptyView method. So, I want to show you how we can add this simple method to the RecyclerView.

First of all, I’ll show how we can create a simple application with the RecyclerView and after it we will implement setEmptyView method for our RecyclerView. In this article, I’ll show you the simplified code.

Simple RecyclerView

Let us begin with dependencies. We should add required dependencies to build.gradle file.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    ...
}

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:25.1.0'
}

Next step is creating layout for an activity for an Activity or a Fragment.

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

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

</RelativeLayout>

A layout for RecyclerView item:

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

    <TextView
        android:id="@+id/title_text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="12dp"
        android:layout_marginRight="12dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:textSize="24sp"/>

</FrameLayout>

We also should implement a simple model class for NotesL

public class Note {
    private String mTitle;

    public Note(String title) {
        this.mTitle = title;
    }

    public String getTitle() {
        return mTitle;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Note note = (Note) o;

        return mTitle != null ? mTitle.equals(note.mTitle) : note.mTitle == null;
    }

    @Override
    public int hashCode() {
        return mTitle != null ? mTitle.hashCode() : 0;
    }

    @Override
    public String toString() {
        return "Note{" +
                "title='" + mTitle + '\'' +
                '}';
    }
}

Next step is to implement an adapter:

public class NotesAdapter extends RecyclerView.Adapter<NoteViewHolder> {
    private List<Note> mNotes;

    public void setNotes(List&lt;Note&gt; notes) {
        this.mNotes = notes;
        notifyDataSetChanged();
    }

    @Override
    public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_note, parent, false);

        return new NoteViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(NoteViewHolder holder, int position) {
        holder.mTitleTextView.setText(mNotes.get(position).getTitle());
    }

    @Override
    public int getItemCount() {
        return mNotes == null ? 0 : mNotes.size();
    }
}

Any Adapter also required ViewHolder.

class NoteViewHolder extends RecyclerView.ViewHolder {

    protected TextView mTitleTextView;

    NoteViewHolder(View itemView) {
        super(itemView);
        mTitleTextView = (TextView) itemView.findViewById(R.id.title_text_view);
    }
}

Finally, we should add an activity:

public class MainActivity extends AppCompatActivity {
    private final static int DELAY = 3000;

    private Handler mHandler;
    private NotesAdapter mAdapter;

    private Runnable updateAdapterRunnable = new Runnable() {
        @Override
        public void run() {
            mAdapter.setNotes(getTmpNotes());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAdapter = new NotesAdapter();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.setAdapter(mAdapter);

        mHandler = new Handler();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mHandler.postDelayed(updateAdapterRunnable, DELAY);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mHandler.removeCallbacks(updateAdapterRunnable);
    }

    private static List<Note> getTmpNotes() {
        List<Note> list = new ArrayList<>();
        list.add(new Note("1st note"));
        list.add(new Note("2nd note"));
        list.add(new Note("3rd note"));
        list.add(new Note("4th note"));
        list.add(new Note("5th note"));
        list.add(new Note("6th note"));
        list.add(new Note("7th note"));
        list.add(new Note("8th note"));
        list.add(new Note("9th note"));
        list.add(new Note("10th note"));
        list.add(new Note("11th note"));
        list.add(new Note("12th note"));
        list.add(new Note("13th note"));
        list.add(new Note("15th note"));
        return list;
    }
}

RecyclerView with a setEmptyView method

The main idea here is to display a view when list is empty. First of all, we should create a custom RecyclerView. I would like to tell you few words about AdapterDataObserver.

The AdapterDataObserver class contain few methods:

  • onChanged
  • onItemRangeChanged(int positionStart, int itemCount, Object payload)
  • onItemRangeChanged(int positionStart, int itemCount)
  • onItemRangeInserted(int positionStart, int itemCount)
  • onItemRangeMoved(int fromPosition, int toPosition, int itemCount)
  • onItemRangeRemoved(int positionStart, int itemCount)

These methods can notify your observer about any changes.

After it, we can create a custom RecyclerView.

public class EmptyRecyclerView extends RecyclerView {
    private View mEmptyView;

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

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

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

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

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

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

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

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

        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }

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

    public void setEmptyView(View view) {
        this.mEmptyView = view;
        initEmptyView();
    }
}

We should also update the layout for an activity or a fragment.

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

    <com.alexzh.recyclerviewsetemptyview.EmptyRecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <TextView
        android:id="@+id/empty_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="add_more_notes"
        android:textSize="36sp" />

</RelativeLayout>

Here we have new view empty_view and we replaced android.support.v7.widget.RecyclerView to our new EmptyRecyclerView (com.alexzh.recyclerviewsetemptyview.EmptyRecyclerView).

We also should update the activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    EmptyRecyclerView recyclerView = (EmptyRecyclerView) findViewById(R.id.recycler_view);
    ...    
    recyclerView.setEmptyView(findViewById(R.id.empty_view));
    ...
}

We can also update Adapter and change public class NotesAdapter extends RecyclerView.Adapter<NoteViewHolder> to `public class NotesAdapter extends EmptyRecyclerView.Adapter.

Right now, we have better user experience.

Finally, I would like to show a gist with simplified code from this tutorial.


Mobile development with Alex

A blog about Android development & testing, Best Practices, Tips and Tricks

Share
More from Mobile development with Alex

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Mobile development with Alex.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.