App Development

Android Fundamentals: Working with the RecyclerView, Adapter, and ViewHolder Pattern

blog-featured-image effective-code-reviews EL-510x296-1

In the mobile development world, regardless of the platform, lists that display data to the user are commonly used in many projects. The Android platform gives us two different types of views that can be leveraged to display lists of data—the ListView and the RecyclerView.

My friend and team member, Brandon Carter, wrote a post about creating performant ListViews , which I highly recommend reading if you need to use a ListView in your project. Today, however, we are going to focus on the more advanced Android view-type to present lists—the RecyclerView.

RecyclerView is a very powerful and flexible tool available to Android developers. Because of this, it’s crucial that we implement it properly so our code strikes a good balance between being as optimized and concise as possible. The following is a tutorial on how I like to implement the RecyclerView, Adapter, and ViewHolder pattern. Ultimately, my plan is to make this a multipart series of tutorials which will begin by using the simple pattern shown below and then move on to using a more advanced and complex usage of this pattern in future installments.

I have created a companion demo project (open source of course!) that you can reference as you read through this post. The demo project can be found on GitHub at https://github.com/willowtreeapps/SimpleRecyclerViewDemo

Get your mind and fingers ready because these posts will be code heavy. That said, let’s jump in and begin creating awesome RecyclerView stuff. And, if you haven’t already, go ahead and create a basic Android project with an empty Activity.


A Simple RecyclerView / Adapter / ViewHolder Pattern

First, we will need a goal for our end product.

  • Create a list containing some number of simple text items.
  • These text items will display the following text: “This is item {itemIndex}” where itemIndex is the index of the ViewModel contained in our list.

Project Setup

We need to begin setting up our project by making sure that the RecyclerView dependency is included in our project libraries. In your app-level build.gradle file add the following dependency: com.android.support:recyclerview-v7:24.2.1.

Hint: You can find the most recent up-to-date version at the official Android Support Library Features page.

The basic dependencies should now look something like the following:

dependencies {
    def VERSION_ANDROID_SUPPORT = '24.2.1'
    ...
    compile "com.android.support:appcompat-v7:$VERSION_ANDROID_SUPPORT"
    compile "com.android.support:recyclerview-v7:$VERSION_ANDROID_SUPPORT"
    ...
}

*Note: Personally, I like to define a variable if there will be multiple dependencies that will need it.

Adding the RecyclerView Widget to the Activity

Now that we have the RecyclerView dependency added to our project, let’s add the view widget to our Activity. In your activity layout file located in the layout resource folder, add the following code:

*Note: The view id is important as we will need to reference it later when instantiating our RecyclerView.

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


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

Creating the Item Layout

In the layout resource folder create a layout file named “item_simple_itemview.xml” and then add a TextView with the id @+id/simple\_text.

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

    <TextView
        android:id="@+id/simple_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="This is some temp text" />
</FrameLayout>

*Note: The view id is extremely important as we will need to reference it later when setting the text to be displayed.

That’s all we need as far as XML files are concerned, now on to the fun stuff!

Creating the ViewModel

First, we need to create a common object that will hold our data. Let’s call it SimpleViewModel.

public class SimpleViewModel {
    private String simpleText;

    public SimpleViewModel(@NonNull final String simpleText) {
        setSimpleText(simpleText);
    }
    
    @NonNull
    public String getSimpleText() {
        return simpleText;
    }

    public void setSimpleText(@NonNull final String simpleText) {
        this.simpleText = simpleText;
    }
}

*Note: I have purposefully created this data object using a getter and setter so that its contents, the String, cannot be directly modified. It also sets us up for the future as the next installment of this series will utilize this class.

Creating the Adapter

Next, we will need an adapter. The adapter is the piece that will connect our data to our RecyclerView and determine the ViewHolder(s) which will need to be used to display that data. At WillowTree, we advocate the separation of concerns principle and consider it best practice to make the adapter as “dumb” as possible. No work performed on the data should live in the adapter. Instead, as will be demonstrated in our project here, we will handle all data manipulation outside of our adapter in our SimpleViewModel class.

Let’s create a class called SimpleAdapter which extends RecyclerView.Adapter.

public class SimpleAdapter extends RecyclerView.Adapter {
    …
}

If using an IDE such as Android Studio, you will immediately notice a red squiggly line which lets us know that there are a basic set of method overrides that will need to be added to your adapter for it to work. These required methods are as follows:

  • onCreateViewHolder(ViewGroup parent, int viewType)
  • onBindViewHolder(RecyclerView.ViewHolder holder, int position)
  • getItemCount()

We will need to add one more method override in our project, getItemViewType(int).

*Hint: In Android Studio you can use the keyboard shortcut CTRL + RETURN to open a context menu which will allow you to find and insert an overridden method.

onCreateViewHolder(ViewGroup, int)

This method is called right when the adapter is created and is used to initialize your ViewHolder(s).

One of the interesting things and, in my opinion, one of the more powerful features that come baked into the RecyclerView, is that the getItemViewType(int position) method allows us to return the actual layout id that the Android framework saves for us as a layout resource. The javadoc for this method reads:

“… Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.”

Returning the layout id resource in this method is very powerful because it allows us to use the resource id with the LayoutInflater in the onCreateViewHolder() method and then return the ViewHolder.

@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
    final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
    return new SimpleViewHolder(view);
}

onBindViewHolder(RecyclerView.ViewHolder, int)

This method is called for each ViewHolder to bind it to the adapter. This is where we will pass our data to our ViewHolder.

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    ((SimpleViewHolder) holder).bindData(models.get(position));
}

getItemCount()

This method returns the size of the collection that contains the items we want to display

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

getItemViewType(int)

This method returns an Integer which represents the view type. Since the Android system stores a static reference to each layout as an Integer in the “R” (resources) class, we can simply return the layout resource id to be used in the onCreateViewHolder() method.

@Override
public int getItemViewType(final int position) {
    return R.layout.item_simple_itemview;
}

Well there it is! We now have a fully functioning RecyclerView Adapter ready to do its thing. Next up, the SimpleViewHolder .

Creating the ViewHolder

Now that we have the SimpleAdapter and SimpleViewModel set up, let’s go ahead and create our SimpleViewHolder.

A ViewHolder is more than just a dumb object that only holds the item’s views. It is the very object that represents each item in our collection and will be used to display it. This powerful object is the reason I was driven to write this tutorial! Often, I see many examples which use the adapter’s onBindViewHolder() method to do the work on the view holder. As I mentioned earlier, we like to separate our concerns and thus, the adapter should actually be the “dumb” object, only connecting the RecyclerView and its data via the view holder.

So, with that said, let us create our SimpleViewHolder and add a method called bindData(SimpleViewModel viewModel). As with the SimpleAdapter, you will notice that upon first creating the SimpleViewHolder there will again be a red squiggly line informing you that you will need to add a constructor to your class. This constructor will take the parent View of the item layout allowing you to setup any views you will need to use when displaying your data. For our project, we only have a TextView so let’s go ahead and set that up:

public class SimpleViewHolder extends RecyclerView.ViewHolder {
    private TextView simpleTextView;

    public SimpleViewHolder(final View itemView) {
        super(itemView);
        simpleTextView = (TextView) itemView.findViewById(R.id.simple_text);
    }
}

Now that we have the basics implemented we will need to add an additional method that we can use to bind our data to that TextView. Go ahead and name that method “bindData” and have it accept our SimpleViewModel as its parameter. Finally, in our bindData(SimpleViewModel) method, let’s set the TextView’s text by retrieving the data from our SimpleViewModel via the getSimpleText() method. Our bindView method should now look something like this:

public class SimpleViewHolder extends RecyclerView.ViewHolder {
    …
    public void bindData(final SimpleViewModel viewModel) {
        simpleTextView.setText(viewModel.getSimpleText());
    }
}

That’s it for our SimpleViewModel! Now, on to hooking everything up in the Activity.

We almost have a complete project that implements the RecyclerView, Adapter, and ViewHolder pattern, which separates each concern into its own object. All we have left to do is to wire everything up in the main Activity. So, onward!

Hooking Everything Up in the Activity

This Activity will be the screen that will display our RecyclerView and all of its containing data to our users. It is important to note here that unlike our SimpleAdapter and SimpleViewHolder, the Activity does not require any overridden methods. However, we do need to add one method override for all of this to work—the override onCreate(Bundle savedInstanceState). In the onCreate method, we need to add a call to the super method and also add the setContentView(int layoutResID) method passing in our Activity’s layout resource id.

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    …
}

For this tutorial, we will initialize the SimpleAdapter and RecyclerView in our onCreate() method. Earlier, when we created our SimpleAdapter, we set it up so that we would need to pass in a list of SimpleViewModel objects, so let’s go ahead and create a private helper method to do that now.

private List<SimpleViewModel> generateSimpleList() {
    List<SimpleViewModel> simpleViewModelList = new ArrayList<>();

    for (int i = 0; i < 100; i++) {
        simpleViewModelList.add(new SimpleViewModel(String.format(Locale.US, "This is item %d", i)));
    }
    
    return simpleViewModelList;
}

All we are doing here is generating 100 SimpleViewModel objects, that pass in a String of “This is item %d” and adding each of those to List<SimpleViewModel> which will then be returned to the caller.

Now that we have our helper method, move to the onCreate() method and instantiate a new SimpleAdapter that passes in our List<SimpleViewModel> generated from the generateSimpleList() helper method.

SimpleAdapter adapter = new SimpleAdapter(generateSimpleList());

After that, we will need to instantiate our RecyclerView using the id resource that we created in our Activity’s XML layout file:

RecyclerView recyclerView = 
(RecyclerView)findViewById(R.id.simple_recyclerview);

Now that we have a RecyclerView, there are a few more things we will need to do to make it work. One of the most important being the LayoutManager. The Android framework provides a few predefined layout managers that we can use to determine how our data will be displayed— LinearLayoutManager, GridLayoutManager, and StaggeredGridLayoutManager. Since we want to display our data as a linear vertical list, we will use LinearLayoutManager. There are multiple ways that you can set the LayoutManager on your RecyclerView —in the XML or programmatically in the Java code. We will go ahead and set our LinearLayoutManager in our Java code:

recyclerView.setLayoutManager(new LinearLayoutManager(this));

*Note: To learn more about RecyclerView.LayoutManager you can visit the official documentation here.

This next step, setting whether or not the RecyclerView has a fixed size, isn’t required but I like to do it because it helps the Android framework optimize the RecyclerView by letting it know in advance the the RecyclerView size will not be affected by the Adapter contents. You can read more about this very useful method in the official Android documentation. With that said, let’s go ahead and implement setHasFixedSize(boolean)like so:

recyclerView.setHasFixedSize(true);

Finally, we will need to attach our SimpleAdapter to the RecyclerView. We do this by using the setAdapter(Adapter) method:

recyclerView.setAdapter(adapter);

That’s it! Now our complete Activity class should look something like this:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SimpleAdapter adapter = new SimpleAdapter(generateSimpleList());
        RecyclerView recyclerView = (RecyclerView)findViewById(R.id.simple_recyclerview);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapter);
    }

    private List<SimpleViewModel> generateSimpleList() {
        List<SimpleViewModel> simpleViewModelList = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            simpleViewModelList.add(new SimpleViewModel(String.format(Locale.US, "This is item %d", i)));
        }

        return simpleViewModelList;
    }
}

Go ahead and run your project in the emulator or on your physical device and you will see a list of items, each displaying the text “This is item {index}” where {index} will be the index number of the item in our list. You should see something similar to the animated gif below.

blog-post-image recyclerviews rf

An animation/gif of what our completed project should look like.

I look forward to seeing you for the next installment of the RecyclerView tutorial where we kick it up a notch and create a slightly more complex project that expands on what we’ve learned here. We will be implementing multiple ViewHolders, the onClickListener interface, and modifying the item data when tapping an item, exciting stuff to be sure!

If you have any questions or suggestions, leave a comment below or reach out to us directly and we will be happy to help in any way that we can.

Have a great day!

Join our team to work with Fortune 500 companies in solving real-world product strategy, design, and technical problems.

Find Your Role
apple event

What We're Taking from Apple's 2019 Hardware Event

Yesterday marked Apple’s annual media event, where they announced the next iterations...

Read the article