Dynamic RecyclerView.Adapter: Mastering Heterogeneous Lists in Android Apps

The Challenge: Displaying Diverse Data

When building Android apps, you often encounter the need to display a list of items with varying layouts and content. This heterogeneous list can include grids, nested horizontal lists, and more. To tackle this challenge, you need a dynamic RecyclerView.Adapter that can handle different ViewHolder types for backend-controlled viewTypes.

Structuring Backend Responses

Before diving into the code, let’s explore how backend responses are typically structured. A server response might look like this:

{ "type": "banner",... }
{ "type": "quote",... }
{ "type": "carousel",... }

Using a JSON deserializer like Gson or Jackson can lead to runtime crashes. One solution is to create a data class, but this approach becomes cumbersome as the number of types grows.

A More Elegant Approach: Moshi Polymorphic JSON Adapter

To streamline the process, you can use Moshi’s polymorphic JSON adapter with Retrofit. Start by adding the required dependencies to your build.gradle file:

dependencies { implementation 'com.squareup.moshi:moshi:1.12.0' }

Next, create Kotlin data classes for each type, using a sealed class and an enum to map the different types:

enum class Type { BANNER, QUOTE, CAROUSEL,... }

sealed class Data { abstract val type: Type }

data class BannerData(val type: Type = Type.BANNER,...) : Data()
data class QuoteData(val type: Type = Type.QUOTE,...) : Data()
...

Creating the UI Models and ViewHolders

For a clean approach, create separate data classes for your UI logic. Start with a marker interface:

interface UIModel

Then, create a utility extension to map each data model to the UI model:

fun Data.toUIModel(): UIModel

Create different UIs for each type, along with their corresponding ViewHolders:

BannerViewHolder
CarouselViewHolder
GridViewHolder
QuoteViewHolder
ContactsViewHolder

Each ViewHolder extends a base class:

abstract class BaseViewHolder<T : UIModel>(view: View) : RecyclerView.ViewHolder(view)

The Dynamic Adapter

Override the getItemViewType() method to inform the RecyclerView.Adapter about the different viewTypes:

override fun getItemViewType(position: Int): Int = viewType.ordinal

In the onCreateViewHolder() method, check the viewType field and create the appropriate ViewHolder:

when (viewType) { BANNER -> BannerViewHolder(...) }

Finally, in the onBindViewHolder() method, call holder.bind(uiList[position]) on the BaseViewHolder.

Adhering to SOLID Principles

While the above code achieves the goal, it’s not perfect. To adhere to SOLID principles, move the ViewHolder instantiation code to each individual ViewHolder class. Then, use the Visitor pattern to delegate the responsibility of checking viewTypes to a separate class:

interface Visitable<T> { fun accept(visitor: Visitor<T>) }

class TypeFactory { fun createViewHolder(viewType: Int): RecyclerView.ViewHolder }

By using the Visitor pattern, you can keep your adapter class lean and avoid modifying it when adding new viewTypes.

Summary

In this article, you learned how to create a dynamic RecyclerView.Adapter that can handle heterogeneous lists with different ViewHolder types for backend-controlled viewTypes. You also saw how to use Moshi’s polymorphic JSON adapter to streamline the process. Finally, you discovered how to adhere to SOLID principles using the Visitor pattern.

Leave a Reply

Your email address will not be published. Required fields are marked *