Handling Complex RecyclerView Screens

MultiType Adapters — Single Responsibility Principle

Problem

Adapters for a RecyclerView can quickly become very complex and error prone when managing many different types of items.

Why is this an issue? Designs of an app change over time. The wonderful adapter you made that handles 3 types of items now has to handle 2 more and you may not be the one adding the extra types. Another dev could now touch code they don’t understand which can cause bugs.

This is also a case of breaking the Single Responsibility Principle. That is, a class should have only one reason to change. The adapter previously had 3 reasons to change, and now 2 more reasons are being added. In addition to that, the new dev who has to make the changes is now touching code unfamiliar and unrelated to what they need to do.

Solution

In order to keep in line with SRP and allow the second dev to easily add additional items we need to break up our adapters.

We can remove all logic related to our data and viewholder into individual adapters with a main adapter to be composed of them. This allows us to compose adapters with as many, or as few items as we want. Adding a new item type only requires making a new item adapter, no other adapter code will have to be touched.

Example

At iHeartRadio we faced this issue in our radio app. We had a custom adapter for each screen that did its job just fine. However, we eventually got a large design overhaul involving many item types on all screens, with certain item types reused extensively across the app. We realized we needed a more efficient and less bug prone solution to our problem. The system would ideally allow us to reuse a lot of our existing logic without creating too much complexity.

The solution we came up with allowed us to meet all of our requirements and anything else that could be thrown at us for a list to show. We took a modular approach to it in the form of our Multi Type Adapter.

A big point was to try and keep things simple. We didn’t want to require anyone to learn a large complex system to manage a list item. With that, we’ll now go through a few steps to use our solution.

First, we need a viewholder:

public class MyViewHolder extends RecyclerView.ViewHolder {



TextView mText;



public MyViewHolder(View itemView) {

super(itemView);

mText = (TextView) itemView.findViewById(R.id.text);

}



public static MyViewHolder create(final ViewGroup parent) {

return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false));

}



public void bindData(final String data) {

mText.setText(data);

}

}

No surprises here, this is a standard Android ViewHolder.

Next we will create a TypeAdapter implementation. A TypeAdapter is an abstract class that manages a single ViewHolder and its required data. In the solution section this is our individual adapter that was mentioned.

public class MyTypeAdapter extends TypeAdapter<String, MyViewHolder> {



@Override

public boolean isMyData(Object data) {

return data instanceof String;

}



@Override

public MyViewHolder onCreateViewHolder(final ViewGroup parent) {

return MyViewHolder.create(parent);

}



@Override

public void onBindViewHolder(final MyViewHolder viewHolder, final String data) {

viewHolder.bind(data);

}

}

Most of this should look familiar if you’ve used a RecyclerView adapter before. What’s new is the isMyData method and the additional type info in the parent class. This is used by our parent adapter to determine which adapter to use for a given type of data. In this case our adapter maps String objects to MyViewHolder, so we can just do a simple check to make sure the data is a String or not.

With our Viewholder and Adapter set up we are ready to make our parent adapter. This is probably the simplest step here. We pass our TypeAdapter to our MultiTypeAdapter and we are good to go! In this case we just pass one TypeAdapter, but we are able to pass any many as we want.

MultiTypeAdapter adapter = new MultiTypeAdapter(myTypeAdapter);

All we need to do now is give our adapter some data. We are going to essentially be passing the adapter a list of plain Objects. To make it look a little nicer there is a helper class called Item which is just a wrapper around a list of Objects. We can pass our item class, or a plain list of Objects to our adapter and we should see the results on our screen.

Items items = Items();

items.add("Item 1");

items.add("Item 2");

items.add("Item 3");

adapter.setData(items);

That’s all there is to it. A minimally invasive system to compose adapters from other adapters.

We use this adapter everywhere in our app. It has made adding additional items to lists very straight forward. Also, since it follows the same basic principles of the standard Android Adapter it made it fairly simple to break down and convert our old adapters into the new ones.