Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Three Lessons Learned From Using RecyclerView

DZone's Guide to

Three Lessons Learned From Using RecyclerView

Stuck on the basics of RecyclerViews? Or do you want to learn how to filter, have multiple different view types in the RecyclerView? The author has been through it all, so here are some lessons he learned along the way.

· Mobile Zone ·
Free Resource

The general consensus among Android developers is that RecyclerViews, introduced in API 21, are what you should use where you might previously have gone with a simpler ListView. You get advantages like better layout managers, easier handling of large datasets and some nice animation capabilities. However, you're bound to hit some snags along the way. I had my fair share, which I'll share with you here. We'll start with a general introduction to the control, and move on to those lessons.

A Quick Intro to RecyclerView

If you haven't had the chance to create a RecyclerView yet, here are the basic steps you need to follow. It takes a little more work than ListView, but once you get your head around it, it makes sense. 

Add Dependency in build.gradle

You will need to add RecyclerView into your build.gradle. There's actually a support library that allows you go back all the way to version 7. Add this line to your dependencies: 

compile 'com.android.support:recyclerview-v7:25+'

Add RecyclerView in Your User Interface

Next, add in the RecyclerView in the XML for the fragment or activity that you are dealing with.

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

In the fragment/activity, load up a reference to your RecyclerView as you would with any other control 

RecyclerView recycler = view.findViewById(R.id.recycler_view);

Create an Adapter 

Providing an adapter for the RecyclerView is how it will deal with rendering your dataset. There are two things you'll need to grasp here; RecyclerView.Adapter and RecyclerView.ViewHolder.

The adapter is a pretty simple structure. In summary, you will need to:

  • Extend RecyclerView.Adapter<RecyclerView.ViewHolder>
  • Store your data, in this case a list of POJOs called DataItem, typically passing through to your constructor.
  • Override the getItemCount() method, which returns the number of items to be displayed
  • Override the onCreateViewHolder method where you will create the ViewHolder for each item
  • Override the onBindViewHolder which binds the data object with the ViewHolder. As you can see, a position parameter is provided, which allows you to retrieve the appropriate DataItem object from your list. This can then be passed through to the ViewHolder.

It would look as follows. I've left out the specifics of the ViewHolder for the time being.

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

  private List<DataItem> listData; 

  public MyAdapter(List<DataItem> data){
     this.listData = data;
  }

  @Override
  public int getItemCount() {
    return listData.size();
  }

  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                     int viewType) {
       //ViewHolder creation goes here
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

      DataItem item = listData.get(position); 
     //ViewHolder data population goes here 
   }
}


Now let's dive into ViewHolders, which are basically a pattern where you have an object that contains both the View and the data to be rendered onto that same View. Typically these ViewHolders are defined as private classes inside of your Adapter. 

All that you really need to do is create an object that extends RecyclerView.ViewHolder and store a reference to the view which is passed through on creation. 

How you pass bind the data object to the ViewHolder is completely up to you. It is usually achieved by creating a simple setter method like setData below. Once the data is passed through, you can then manipulate the view as you would with any View.

private class MyViewHolder extends RecyclerView.ViewHolder{
  //a reference to the View
  private View itemView; 

  private MyViewHolder(View view){
  super(view);
  }

  public void setData(DataItem item){
    //place data from the object onto the view 
    TextField text = itemView.findViewById(R.id.item_text); 
    text.setText(item.getName());
  }
}

Now back to the adapter, we can fill in those methods related to ViewHolders


  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                     int viewType) {
    //First inflate the view 
       View itemView = LayoutInflater.from(parent.getContext())
         .inflate(R.layout.item_view, parent, false);
       //create the ViewHolder object
       MyViewHolder viewHolder = new MyViewHolder(itemView); 
       return viewHolder;
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

      DataItem item = listData.get(position); 
     //ViewHolder data population goes here 
      ((MyViewHolder)holder).setData(item); 
   }

In the create method, the view that we wanted to use (in this example item_view.xml) is inflated, and passed through to the ViewHolder. The bind method simply passes the object through.

Assigning a Layout Manager 

Now that we have an adapter and ViewHolder in place, we can complete the creation of the RecyclerView in the Fragment/Activity.


List<DataItem> data = new ArrayList<>(); 
//populate your data list, or retrieve it from somewhere else 

MyAdapter adapter = new MyAdapter(data); 
recyclerView.setAdapter(adapter);

You can also assign any layout manager you wish. In this example I close a simple LinearLayoutManager 

recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

LinearLayoutManager displays items in a vertical or horizontal scrolling grid. You can also use GridLayoutManager to achieve a grid view, or StaggeredGridLayoutManager to use a staggered grid. These are just the built-in layout managers; you can always just create your own!

There's obviously a lot more that you can do with your RecyclerView, but the above should be enough to get you going. Onto the lessons! 

Lesson 1: Be Careful With setHasStableIds()

I had the strangest problem where the content I was passing through to the adapter was in the correct order, and looked like it should display without issue. However, it seemed that some items were rendered with the incorrect view. It seemed that item 1 was being rendered a second time in position 5.

This lead to lots of debugging, and it wasn't until I stumbled across this question on StackOverflow that I realized where I had gone wrong. In my haste to integrate another library, I had added the following line in the constructor of my adapter: 

setHasStableIds(true);

The reason I had added this? The widget required that all my items had stable IDs, so I just forced the adapter to think they did. It resulted in ViewHolders getting reused, which was certainly not my intention. Unless you're 100% sure what you're doing, don't add this line in! 

Lesson 2: Multiple View Types Is (Fairly) Easy

The examples you'll find, including my tutorial above, cover the basic case pretty well; where you just have one type of view repeated for each item. There will inevitably be cases where you need to have completely different views for certain items.

This is really easy to do though. Building on the tutorial above, here are the steps to follow:

  • Add a way of distinguishing your objects. If your objects need a different representation on the UI add a way of differentiating these objects. This could be through a type variable for example. Here I've created two different types as constants.   Naturally, there are lots of other ways to distinguish between types in Java!

  • public DataItem{
      public static final int TYPE_ONE = 0; 
      public static final int TYPE_TWO = 1;
    
      private int type; 
    
      public int getType(){
         return type;
      }
    }
  • Define different view holders for each type. Where we had one ViewHolder created previously, you should now have a ViewHolder for each type. This is important as you will be passing through a different structure of View object for each.
  • Override the getItemViewType method. This part is key, as the int returned by this method will be used in the create method in your adapter. This method allows you to handle items at the given position. 
  • 
        @Override
        public int getItemViewType(int position){
          if (listData != null) {
            DataItem item = listData.get(position);
            if (item != null) {
              return item.getType();
            }
          }
          //fallback
          return 0;
        }


  • Create the correct ViewHolder for the given type. Modify the onCreateViewHolder method so that it uses the second type variable in order to create the correct view and passes that through to the right type of ViewHolder.  As you can see below, based on viewType a different View and ViewHolder are built
  • @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                          int viewType) {
            View itemView;
            switch(viewType){
                case DataItem.TYPE_ONE:
                    itemView = LayoutInflater.from(parent.getContext())
                            .inflate(R.layout.item_type_one, parent, false);
    
                    TypeOneViewHolder viewHolder = new TypeOneViewHolder(itemView);
                return viewHolder;
    
                case DataItem.TYPE_TWO:
                    itemView = LayoutInflater.from(parent.getContext())
                            .inflate(R.layout.item_type_two, parent, false);
    
                    TypeTwoViewHolder viewHolder = new TypeTwoViewHolder(itemView);
                return viewHolder;
            }
            return null;
        }


  • Bind the object to the view. All that remains is to bind the object to the view
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    
            DataItem item = listData.get(position);
    
            //work out odd-even color
            if(item.getType() == DataItem.TYPE_ONE){
    TypeOneViewHolder viewHolder = (TypeOneViewHolder)holder;  
                viewHolder.setData(item);          
            }
            else{
    TypeTwoViewHolder viewHolder = (TypeTwoViewHolder)holder;  
                viewHolder.setData(item);              
            }
    
        }

Lesson 3: How to Filter Your List 

Filtering is another straightforward task. Once you determine the criteria for what should be displayed, via a search filter or some other mechanism, you can call pass through these flags to your adapter. Typically, you will just add a filter method:

public void filter(boolean showNew, boolean showAll, List<DataItem> data){
   //clear the local copy 
   this.listData.clear(); 
   for(DataItem item: data){
      if(showAll || (showNew && data.isNew()){
         this.listData.add(item);
      }
   }
   notifyDataSetChanged();
}

Some crucial points in this code: 

  • Make sure that your adapter stores a copy and not a reference to the original data. Otherwise, when filtering out for the adapter, you'll filter that original data set.
  • In this example I pass through the full set of data as a parameter. You could also do this filtering seperately, and pass in the filtered data.

However, the most important part of all is where notifyDataSetChanged() is invoked, as this will cause the RecyclerAdapter to refresh. 

Bonus Lesson: Consider Third-party Libraries 

I wanted to get a few bonus features without too much work writing boilerplate code, so I used SuperRecyclerView. This gives you the ability to easily set views to use when the adapter is empty, progress bars while the adapter isn't set, infinite scrolling and easy refresh handling. 

It's very clean to replace your existing RecyclerView: 


    <com.malinskiy.superrecyclerview.SuperRecyclerView
        android:id="@+id/recycler_view"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_empty="@layout/emptyview"
        app:layout_moreProgress="@layout/view_more_progress"
        app:mainLayoutId="@layout/layout_recyclerview_verticalscroll"
        app:recyclerClipToPadding="false"
        app:recyclerPadding="0dp"
        app:scrollbarStyle="outsideInset">

The view to use when there is no data available is expressed in the layout_empty property. In this example, you would need to create a view_more_progress.xml layout. Similarly, you would need to create a view_more_progress.xml view for the infinite scrolling capability. 

Adding a refresh listener is as simple as getting your Fragment/Activity to implement SwipeRefreshLayout.OnRefreshListener. All you need to do is add an onRefresh method: 


    @Override
    public void onRefresh() {
        //get the data and when done, stop refreshing
        recyclerView.setRefreshing(false);
    }

Add this listener to the SuperRecyclerView, and specify the colors to use when animating the refresh progress bar. 

recyclerView.setRefreshListener(this);
recyclerView.setRefreshingColorResources(android.R.color.holo_orange_light, android.R.color.holo_blue_light, android.R.color.holo_green_light, android.R.color.holo_red_light);
Topics:
android ,tips ,recyclerview ,mobile

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}