Mobile development

Alex Zhukovich

How to setEmptyView to RecyclerView


Today I would like to start new series about tips for RecyclerView. Almost all applications display information in the lists. We have two basic components for displaying list of data in Android. It’s ListView and RecyclerView. The RecyclerView is more efficient component from an Android documentation.

After first run some applications has an empty list of data, as example Notes or 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 and I would like to show you how we can add this simple method to the RecyclerView.

First, 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>

And 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 Notes

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 + '\'' +
                '}';
    }
}

And we should implement an adapter.

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

    public void setNotes(List<Note> 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();
    }
}

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

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

So, we have

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, like onChanged, onItemRangeChanged, onItemRangeChanged, onItemRangeInserted, onItemRangeMoved, onItemRangeRemoved.

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 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<NoteViewHolder>.

Right now, we have better user experience.

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

If this article was helpful to you, please share it. You can also subscribe to me on Twitter and get more useful information connected to Android development.

Share Share on Reddit0Share on VKTweet about this on TwitterShare on LinkedIn0Share on Google+0Share on Facebook2Flattr the authorEmail this to someoneShare on Tumblr0
« »

© 2017 Mobile development. Theme by Anders Norén.