Android Dynamic Views with RecyclerView

Gustavo Santorio
Android Playground
Published in
4 min readJun 10, 2020

During several years as an Android Developer, I always had the RecyclerView as a partner for many of the features that I delivered for all the companies that I’ve worked for. But each new feature often leads me to face a harder challenge, which brings me experience and some bugs as well.

One of the greatest challenges that I’ve come across is to bind different Views and ViewHolders in the same List. This way I need to implement the getItemViewType(…) method and manually pass to my RecyclerView’s Adapter all the Integer Types available, and to share views between Screens and Adapters I must manually put ViewHolders on it.

I’ve come with a solution that solves all (or almost all) the problems we have while dealing with multiple ViewHolders type. In this article, I will explain the Dynamic Structure for RecyclerViews.

Let's talk about code!

The first thing We need to talk about is how to let view types dynamic, and the answer is your model!

We can create a Model that tells us the Type of a view and how to cast it. My model has a sample structure with two parameters (Key and Value) that tell the adapter the view type.

data class SimpleVO(val key: String, val value: Any?)

The key is a String parameter that represents my view type, but the getItemViewType(…) needs to return an Integer, then how can we convert this?

I created an Enum class that is called DynamicComponent. This class has all my view types and all I have to do is call what type is the key that was passed.

enum class DynamicComponent() {
FIRSTCOMPONENT,
SECONDCOMPONENT;
companion object {
fun getDynamicComponentByName(name: String?): DynamicComponent? = name?.let {
return try
{
valueOf(it.toUpperCase(Locale.getDefault()))
} catch (ignored: Exception) {
null
}
}
}
}

In my adapter, I just need to call this method and get the Enum’s ordinal value.

override fun getItemViewType(position: Int): Int {
vos[position].let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
return it
}
}
return super
.getItemViewType(position)
}

And that’s it! Now you have a dynamic view type. All you need to do is register your keys in DynamicComponent class, and then create your models with your key and value parameters like this:

val list = 
listOf(
SimpleVO(
DynamicComponent.FIRSTCOMPONENT.name,
/*or just FIRSTCOMPONENT string*/
"any value you want"))

PS: You can retrieve this model from your backend, and delegate the responsibility to choose what view and order will be shown to the user.

But not all problems are solved! You still have to create your ViewHolders and bind it. For this, there is a solution as well.

If you want your adapter to be really Dynamic you also need to create and bind your holders dynamically. I created a class named ViewRenderer, and it has the responsibility to get and create the correct ViewHolder, and to bind your view. The abstract class looks like this:

abstract class ViewRenderer<VH : RecyclerView.ViewHolder>
(val viewType: Int) {
abstract fun bindView(model: SimpleVO,
holder: VH)

abstract fun createViewHolder(parent: ViewGroup): VH
}

Then, you can implement this abstract class and create your own ViewRenderer and ViewHolder, like this:

class FirstComponentViewHolder(val textView : TextView) : RecyclerView.ViewHolder(textView)

class FirstComponentViewRenderer : ViewRenderer<FirstComponentViewHolder>(DynamicComponent.FIRSTCOMPONENT.ordinal){

override fun bindView(model: SimpleVO,
holder: FirstComponentViewHolder) {
holder.textView.text = model.value as? String
}

override fun createViewHolder(parent: ViewGroup)
: FirstComponentViewHolder =
FirstComponentViewHolder(TextView(parent.context))

}

In createViewHolder(…) method you need to Create your ViewHolder instance as Declared in class constructor. To inflate the view you can just instantiate it, ou use LayoutInflater to get an xml file using the parent context that is received as parameter.

In bindView(…) you receive the model and ViewHolder as parameter. Then, you can bind your view as you want. Just access it in the holder and set all the data is needed.

But how will the adapter know your renderer? For this, the solution is a list of available renderers that you can register and call in onCreateViewHolder(…) and onBindViewHolder(…). Like this:

override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder =
renderers.get(viewType)?.let {
return it
.createViewHolder(parent)
} ?: EmptyViewHolder(FrameLayout(parent.context))

override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
position: Int) {
vos[position]
.let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
renderers
.get(it)?.bindView(simpleVO,
holder)
}
}
}

But to get a renderer you need to register first. Create a registration method like this:

var renderers = SparseArray<ViewRenderer<RecyclerView.ViewHolder>>()

fun registerRenderer(renderer: ViewRenderer<*>) {
if (renderers.get(renderer.viewType) == null)
renderers.put(renderer.viewType,
renderer as ViewRenderer<RecyclerView.ViewHolder>)
}

To call it, just create a ViewRenderer Instance, and call Adapters registerRenderer(…) method like this:

val dynamicAdapter = DynamicAdapter(listOf())
val viewRenderer = FirstComponentViewRenderer()

dynamicAdapter.registerRenderer(viewRenderer)

You can register all renderers you want, and every renderer will handle ViewHolder’s creation and binding.

Github link: https://github.com/GustavoHSSantorio/Dynamic-Adapter

Conclusion

With this structure, you can now use a single adapter for any screen and view component you need, and just create your ViewRenderes and Views as I have shown above. You can use Frameworks like DataBinding to empower this solution too.

Any doubt let me know in comments please. Thanks!!

--

--