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