Creating a Multiple-View-Type RecyclerView Using the Visitor Pattern

When RecyclerViews start getting big, we have to start getting smart.

Sean Larson
4 min readMar 17, 2019

RecyclerViews are a common component in most modern Android apps. There are several ways to go about implementing them and some implementations are better than others. So, what is the best way to do RecyclerViews? The answer, as always with software, is it depends on what we want to display in a scrollable list and how we want to display it.

Before we dive into an explanation of the visitor pattern, let’s take some time to talk about how to use a RecyclerView in its most vanilla fashion. If we’re only displaying a list of homogenous views, set-up is fairly easy. We simply create an adapter class, extend the RecyclerView.Adapter, define a ViewHolder all in the same file, and we’re off and running.

Here we have the Item class:

Lastly, we initialize the adapter and set it to our RecyclerView. This particular implementation assumes some asynchronous behavior regarding fetching and setting a list of items. For instance, let’s say there is an API that returns data from a web call.

The code above assumes you have a list of items already and we are instantiating the adapter from an Activity or Fragment

This is great! Nice and easy. But what if we need to display different types of views based on the type of item in the list? ( item.type ) We will most likely want to inflate additional views for these different view types. In that case, we will need to override getItemViewType(...) in order to provide the appropriate viewType argument to the subsequent methods. Next, additional logic will be required in the onCreateViewHolder(...) method. Then, we will want to create additional ViewHolders as logic regarding our data might be different. Here is what it might look like if it all stays in the same file:

Don’t forget that in this case we can no longer specify BasicViewHolder as the ViewHolder type when we extend the RecyclerView.Adapter. We must use the parent ViewHolder class.

We just doubled the size of our Adapter class. It’s now easy to imagine how quickly this can escalate and swell to an unwieldy size given any additional view types or logic. So how do we contain this beast from growing to epic proportions? We should probably break out the ViewHolder classes into their own files and we will do just that. However, removing the ViewHolder classes as they are will do little if the logic for each view is complex. All of that logic presently lives in our onBindViewHolder(...) method. If only there were a way to move all that code into the ViewHolder class when we break it out. 😉
The first thing we need to do is create an abstract class for our ViewHolders to implement. Behold the BaseViewHolderClass:

We need to create a ViewHolder class and extend the BaseViewHolder; that way, we will have provided a bind method for the adapter to bind the model data to the relevant viewHolder.

We can easily handle all view logic in the ViewHolder once it’s bound in the adapter.

In our adapter’s onBindViewHolder(…) method we can remove the when statement and just tell the holder to bind the data. It does not matter which type of ViewHolder it is at this point for the bind() method only cares that it gets the data.

Sweet! This is cleaning up nicely. What else might we extract? How about the logic in getItemViewType(...)? That would be even sweeter.

Enter the Visitor Pattern. The Visitor Pattern is one of the many design patterns put to paper by the Gang of Four (Four dudes who wrote a highly influential book called Design Patterns: Elements of Reusable Object-Oriented Software in the 90s. It’s still highly relevant in case there is any concern on that matter). The Visitor Pattern also allows us to adhere to the Open/Closed principle (the O of the S.O.L.I.D. principles: Open to extension, closed to modification). Thus we extend functionality of the adapter without having to change it. Our goal here is to be able to add as many view types as we like without having to ever change code in the adapter. We are already very close to getting the adapter to that state but before we can, we need to set up a few other things.

First, let’s modify the Item model. Item must be able to be “visited” so that some typeFactory may determine the item’s type. For the sake of keeping it D.R.Y., let’s create an interface called ItemViewModel that will allow all of our models to accept a visitor to determine that model’s type.

Now we need to implement it in the Item model.

Our Item model got some new stuff. It’s now the owner of all the information pertaining to itself such as what types it can be and what views it can occupy. Seems reasonable, right?

The ViewHolderTypeFactory (The Visitor) … and a bit more.

The idea is that an Item object will have a method that accepts the viewHolderTypeFactory and returns the appropriate ViewHolder type: val type = item.type(viewHolderTypeFactory). In essence, the viewHolderTypeFactory is “visiting” the Item object to determine the ViewHolder type. Additionally, the primary reason ViewHolderTypeFactory exists should be evident by its name — it needs to produce a ViewHolder. So we need to implement a create() method that takes the parent view and the view type and creates the appropriate ViewHolder.

That’s it! Now we can finally refine our adapter to its Open/Closed-compliant state. Check it out:

At scale, this pattern is great for an app that has multiple RecyclerViews that display multiple views. Once click handlers are introduced, we can add as many view types as needed without having to touch the adapter. As a final note, each RecyclerView should have its own ViewFactory as it will most likely use different data models than the others.

Update:

I’ve written a follow up on how we might approach click handling. Check it out: Click-Handling for MultiView RecyclerViews

Happy Recycling!

--

--