RecyclerView Adds EmptyView

I am trying to implement EmptyView on my RecyclerView Adapter , but I am not getting any result.

I followed this tutorial and this tip , but no one worked for me.

I implemented:

 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; 

But this does not allow me to compile, because they are different ViewHolder , because I created two ViewHolder classes, but they extends Recycler.ViewHolder , so I do not get it ...

I am trying to do this because I have SearchView and when I want the list to be empty, it shows EmptyView , I do it programmatically, but I prefer to add as layout because I donโ€™t know how to make TextViews and Buttons programmatically.

Also if I put

 return dataList.size() > 0 ? dataList.size() : 1; 

It gives me an error because the index is 0.

I debugged viewType and is always 1, then it will not join the if condition ...

Deep on Android I found this:

 /** * Return the view type of the item at <code>position</code> for the purposes * of view recycling. * * <p>The default implementation of this method returns 0, making the assumption of * a single view type for the adapter. Unlike ListView adapters, types need not * be contiguous. Consider using id resources to uniquely identify item view types. * * @param position position to query * @return integer value identifying the type of the view needed to represent the item at * <code>position</code>. Type codes need not be contiguous. */ public int getItemViewType(int position) { return 0; } 

But the fact is that no changes are changed.

EDIT

I almost did it, I did this:

  @Override public int getItemViewType(int position) { return list.size() > 0 ? list.size() : 1; } 

But sometimes it returns 0 when size () is 0 ... I donโ€™t understand, I use this SearchView , and sometimes when I print a letter that does not correspond to any element of the list, it is not displayed, and sometimes it ...

The following also happens: when the popup windows of the layout displayed on the left screen on the screen, when I put it on the center , but I think this is a problem with the RecyclerView , because the layout is placed inside it.

RecyclerView Layout:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:id="@+id/rtpew" android:layout_centerInParent="true" app:layout_behavior="@string/appbar_scrolling_view_behavior" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/linearpew"> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </RelativeLayout> 

And this is my emptylayout :

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ImageViewSearchFail" android:src="@drawable/sadface" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:layout_gravity="center" android:textSize="@dimen/15dp" android:layout_marginTop="4dp" android:text="foo" android:layout_below="@+id/ImageViewSearchFail" android:layout_centerHorizontal="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ButtonAddEntity" android:text="foo" android:background="?android:selectableItemBackground" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout> 

Another way that I thought was to programmatically execute it as follows:

  @Override public boolean onQueryTextChange(String query) { final ArrayList<List> filteredModelList = filter(mModel, query); mAdapter.animateTo(filteredModelList); rv.scrollToPosition(0); if(query.isEmpty()){ //Here } return true; } 

AND:

 private ArrayList<List> filter(ArrayList<List> models, String query) { query = query.toLowerCase(); final ArrayList<List> filteredModelList = new ArrayList<List>(); for (List model : models) { final String text = model.getRedName().toLowerCase(); if (text.contains(query)) { filteredModelList.add(model); } } if (filteredModelList.size()<0) { //HERE } else{ //Delete the views added } return filteredModelList; } 

PROBLEMS

-I only add the view using the @Jimeux answer , but I would like to do it on the Adapter , I got it, but it does not always show view , even if list empty.

- At the time when putting emptyview.xml places inside the RecyclerView , since I put all this xml in the center, it shows on the right. I tried to add xml programmatically, but it's like chaos ....

+6
android android-recyclerview
Jan 20 '16 at 0:07
source share
5 answers

Since you need to handle two different kinds of views, it would be easier to use an intermediate list of the business object to more easily associate them with the views. The idea is to have a placeholder view in your list to represent an empty state. Defining an intermediate level is extremely useful in this sense, allowing you to consider possible changes that will be applied to your list in the future (for example, adding item types). Moreover, you can more clearly separate your business model from the ui representation (for example, you can implement methods that return ui settings based on the internal state of model objects).

You can proceed as follows:

  • Define a highlighted abstract type for list items (such as ListItem) to wrap your business objects. Its implementation may be something like this:

     public abstract class ListItem { public static final int TYPE_EMPTY = 0; public static final int TYPE_MY_OBJ = 1; abstract public int getType(); } 
  • Define a class for each type of your list item:

     public class EmptyItem extends ListItem { @Override public int getType() { return TYPE_EMPTY; } } public class MyObjItem extends ListItem { private MyObj obj; public ContactItem(MyObj obj) { this.obj = obj; } public MyObj getMyObj() { return obj; } // here you can also add methods for simplify // objects rendering (eg get background color // based on your object internal status) @Override public int getType() { return TYPE_MY_OBJ; } } 
  • Create your list.

     List<ListItem> mItems = new ArrayList<>(); if (dataList != null && dataList.size() > 0) { for (MyObj obj : dataList) { mItems.add(new MyObjItem(obj)); } } else { mItems.add(new EmptyItem()); } 

    This is the most important part of the code. You have many options to create this list. You can do this inside the RecyclerView adapter or outside, but it is very important to handle the possible changes correctly. This is necessary to use Adapter notification methods. For example, if you create a list in an adapter, it should probably also provide methods for adding or removing model elements. For example:

     public void addObj(MyObj obj) { if (mItems.size() == 1 && mItems.get(0).getType() == ListItem.EMPTY_TYPE) { mItems.clear(); } mItems.add(new MyObjItem(obj)); notifyDataSetChanged(); } 
  • Define an adapter for your RecyclerView, working on the list defined at point 3. It is important here to override the getItemViewType method as follows:

     @Override public int getItemViewType(int position) { return mItems.get(position).getType(); } 

In addition, the ViewHolder type must be RecyclerView.ViewHolder (unless you decide to create an intermediate class even then).

  1. Then you need to have two layouts and a ViewHolder for empty and obj business objects. Adapters should take care of this as follows:

     @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ListItem.TYPE_EMPTY) { View itemView = mLayoutInflater.inflate(R.layout.empty_layout, parent, false); return new EmptyViewHolder(itemView); } else { View itemView = mLayoutInflater.inflate(R.layout.myobj_layout, parent, false); return new MyObjViewHolder(itemView); } } @Override public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) { int type = getItemViewType(position); if (type == ListItem.TYPE_EMPTY) { EmptyItem header = (EmptyItem) mItems.get(position); EmptyViewHolder holder = (EmptyViewHolder) viewHolder; // your logic here... probably nothing to do since it empty } else { MyObjItem event = (MyObjItem) mItems.get(position); MyObjViewHolder holder = (MyObjViewHolder) viewHolder; // your logic here } } 

Of course, as I wrote at the beginning, you do not need to strictly define intermediate types for representing ui (EmptyItem and MyObjItem). You can even just use the MyObj type and create a specific configuration for it, representing an empty placeholder. This approach is probably not the best if in the future you need to make your logic more complex by including, for example, new types of list items.

+3
Jan 25 '16 at 9:09
source share

A compilation error probably occurs because you are extending the RecyclerView.Adapter with the main ViewHolder as a general argument.

You have to do it like

 YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 

And then draw ViewHolders (you can reuse getViewType(position) here). Remember to also switch the ViewHolder type to your methods.

+2
Jan 20 '16 at 0:16
source share

If I were you, I would not put an empty view in the adapter at all. Place it under the linearpew layout that contains the RecyclerView and will hide / show it as your data changes. With this setting, you can easily add a download view, an error view, etc.

Here's some simplified code from one of my apps to give you some ideas. @Bind comes from Butter Knife if you are not familiar with it. You can also check out the Jake Wharton u2020 project for more RecyclerView ideas.

 //fragment_layout.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:id="@+id/content"> </FrameLayout> <include layout="@layout/status_views" /> </RelativeLayout> //status_views.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <LinearLayout style="@style/ListStatusView" android:id="@+id/empty_view"/> <LinearLayout style="@style/ListStatusView" android:id="@+id/error_view"/> <LinearLayout style="@style/ListStatusView" android:id="@+id/loading_view" android:padding="30dp"/> </LinearLayout> //MyFragment.java @Bind(R.id.content) protected ViewGroup contentView; @Bind(R.id.loading_view) protected ViewGroup loadingView; @Bind(R.id.empty_view) protected ViewGroup emptyView; @Bind(R.id.error_view) protected ViewGroup errorView; @Bind({R.id.loading_view, R.id.error_view, R.id.empty_view, R.id.content}) protected List<ViewGroup> stateViews; protected void activateView(View view) { for (ViewGroup vg : stateViews) vg.setVisibility(View.GONE); view.setVisibility(VISIBLE); } @Override public void onActivityCreated(@Nullable Bundle state) { super.onActivityCreated(state); if (state == null) { activateView(loadingView); loadData(); } else if (data.isEmpty()) activateView(emptyView); else activateView(contentView); } 

Edit: here is a simplified version without Butter Knife.

 private ViewGroup contentView; private ViewGroup emptyView; @Override protected void onCreate(@Nullable Bundle state) { super.onCreate(state); setContentView(R.layout.activity_main); contentView = (ViewGroup) findViewById(R.id.content_view); emptyView = (ViewGroup) findViewById(R.id.empty_view); } @Override public boolean onQueryTextChange(String query) { final ArrayList<List> filteredModelList = filter(mModel, query); mAdapter.animateTo(filteredModelList); rv.scrollToPosition(0); if(query.isEmpty()){ contentView.setVisibility(View.GONE); emptyView.setVisibility(View.VISIBLE); } else { contentView.setVisibility(View.VISIBLE); emptyView.setVisibility(View.GONE); } return true; } <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rtpew" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true"> <LinearLayout android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout> <RelativeLayout android:id="@+id/empty_view"> <ImageView android:src="@drawable/sadface"/> <TextView android:text="foo"/> <Button android:id="@+id/ButtonAddEntity"/> </RelativeLayout> </RelativeLayout> 
+2
Jan 20 '16 at 2:12
source share

Follow the steps below in order

one). Since you have two types of views for your RecyclerView element, your adapter declaration should look like generic

 public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 

and your ViewHolders for list item and empty view should extend RecyclerView.ViewHolder as follows

  static class ListItemViewHolder extends RecyclerView.ViewHolder { public ListItemViewHolder(View itemView) { super(itemView); // initialize your views here for list items } } static class EmptyViewViewHolder extends RecyclerView.ViewHolder { public EmptyViewViewHolder(View itemView) { super(itemView); // initialize your views here for empty list } } 

2). You need Override getItemCount() and getItemViewType()

 @Override public int getItemCount() { return yourList.size() > 0 ? yourList.size() : 1;// if size of your list is greater than 0, you will return your size of list otherwise 1 for the empty view. } @Override public int getItemViewType(int position) { if (yourList.size() == 0) { return VIEW_TYPE_EMPTY; } return position; } 

3). Your onCreateViewHolder() will now look the same.

 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_EMPTY) { return new EmptyViewViewHolder(mLayoutInflater .inflate(R.layout.empty_view_layout, parent, false)); } else { return new ListItemViewHolder(mLayoutInflater .inflate(R.layout.row_list_item, parent, false)); } } 

four). The same checks that you should apply in onBindViewHolder() as well

 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (getItemViewType(position) == VIEW_TYPE_EMPTY) { EmptyViewViewHolder emptyViewViewHolder = (EmptyViewViewHolder) holder; // set values for your empty views } else { ListItemViewHolder listItemViewHolder = (ListItemViewHolder) holder; // set values for your list items } } 

5). Finally Override your SearcView.setOnQueryTextListener()

 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { return false; } @Override public boolean onQueryTextChange(String newText) { currentSearchKeyword = newText.trim(); if(currentSearchKeyword.iseEmpty()){ yourList.clear(); yourAdapter.notifyDataSetChanged(); }else{ // there will two cases again 1). If your currentSearchKeyword matchces with list results, add that items to your list and notify your adapter. 2) If the currentSearchKeyword doesn't matched with list results, clear your list and notify your adapter; } return false; } }); 

Hope this helps you let me know if any problems arise.

+2
Jan 24 '16 at 10:33
source share

Here is what you can try:

1. Replace

 EmptyViewHolder evh = new EmptyViewHolder(v); 

from

 RecyclerView.ViewHolder evh = new EmptyViewHolder(v); 

This is probably why compilation fails.

2. Replace

 @Override public int getItemViewType(int position) { return list.size() > 0 ? list.size() : 1; } 

from

 @Override public int getItemViewType(int position) { return list.get(position) != null ? 1 : 0; } 

For this to work, you must insert a null object whenever you want to show EmptyView :

 int progressPosition = list.size(); list.add(null); adapter.notifyItemInserted(progressPosition); 

and remove the null object if you want to hide the EmptyView :

 int progressPosition = existingList.size() - 1; existingList.remove(progressPosition); adapter.notifyItemRemoved(progressPosition); 

In addition, you should change your onCreateViewHolder() method as follows:

  @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 1) { // inflate your default ViewHolder here ... } else { // inflate the EmptyViewHolder here } } 

I believe we have discussed this before ... see this question for a detailed discussion of this issue.

3. Instead of using SearchView consider using AutoCompleteTextView with Filter . It may be easier to integrate with your RecyclerView Adapter . See this answer for an example.

I will update this answer as I better understand your question ... try this and update.

+1
Jan 22 '16 at 5:25
source share



All Articles