My contrived solution “How to create a RecyclerView Adapter”
Recently, I have become less likely to use xml markup to make a screen for Activity
or Fragment'а
.
Basically I write UI code and I really like it 🙂
And I came across a problem “template creation of an adapter for RecyclerView
“.
Well, I’ll show how I came to my solution, but I’ll immediately give a warning, maybe my solution may be bad or there is a hidden error in it that is beyond my control.
Therefore, I ask you without “raw criticism” in terms of: “what kind of nonsense did you write” or “shit code”.
Comment: The solution given here is used only where the UI is written in code without, once again, without applying xml markup.
Well, pour yourself some coffee, prepare cookies and let’s go!
Step 1: CoreAdapter with Generic Type
First of all, I created a regular RecyclerView
adapter, analyzed it and asked myself the question: how can I abstract the creation of the view and its binding from the adapter?
class CoreViewHolder(view: View) : RecyclerView.ViewHolder(view) {}
class CoreAdapter(
private val items: List<String>
) : RecyclerView.Adapter<CoreViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : CoreViewHolder {
}
override fun onBindViewHolder(holder: CoreViewHolder, position: Int) {
}
override fun getItemCount() = items.size
}
If we abstract, then we need to abstract from the type of the list element, so we make our adapter generic:
class CoreViewHolder<T>(view: View) : RecyclerView.ViewHolder(view) {}
class CoreAdapter<T>(
private val items: List<T>
) : RecyclerView.Adapter<CoreViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : CoreViewHolder<T> {
}
override fun onBindViewHolder(holder: CoreViewHolder<T>, position: Int) {
}
override fun getItemCount() = items.size
}
Well, let’s move on.
ViewHolderContainer abstract class and BindListener interface
Here I had to tinker, because the challenges onCreateViewHolder
And onBindViewHolder
occur separately from each other.
First I created an abstract class ViewHolderContainer
which encapsulates the creation of a view:
abstract class ViewHolderContainer<T> {
abstract fun view(ctx: Context) : View
fun holder(parent: ViewGroup) : CoreViewHolder<T> {
val view = view(parent.context)
return CoreViewHolder(view)
}
}
Okay, the creation of the view will take place in the context of the implementation of our abstract class, so when overriding the method view
we will have access to other methods ViewHolderContainer'а
.
Let’s add a new parameter to the constructor of our adapter viewHolderContainer
and add the method onCreateViewHolder
:
class CoreAdapter<T>(
private val items: List<T>,
private val viewHolderContainer: ViewHolderContainer<T>
) : RecyclerView.Adapter<CoreViewHolder<T>>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : CoreViewHolder<T> {
return viewHolderContainer.holder(parent)
}
override fun onBindViewHolder(holder: CoreViewHolder<T>, position: Int) {
}
override fun getItemCount() = items.size
}
Now we need an interface whose method will be called when the adapter calls onBindViewHolder
I called such an interface BindListener
:
fun interface BindListener<T> {
fun onBind(pos: Int, item: T)
}
Next, we need to pass this interface to our CoreViewHolder'у
and don’t forget to add the method bind
:
class CoreViewHolder<T>(view: View, private val listener: BindListener<T>) : RecyclerView.ViewHolder(view) {
fun bind(position: Int, item: T) {
listener.onBind(position, item)
}
}
Everything seems to be obvious here, in the adapter method onBindViewHolder
our method will be called bind,
previously defined in CoreViewHolder'е
:
class CoreAdapter<T>(
private val items: List<T>,
private val viewHolderContainer: ViewHolderContainer<T>
) : RecyclerView.Adapter<CoreViewHolder<T>>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : CoreViewHolder<T> {
return viewHolderContainer.holder(parent)
}
override fun onBindViewHolder(holder: CoreViewHolder<T>, position: Int) {
holder.bind(position, items[position])
}
override fun getItemCount() = items.size
}
And in the method bind
we pull our interface, the implementation of which is passed CoreViewHolder
in constructor!
Let’s return now to our abstract class:
abstract class ViewHolderContainer<T> {
abstract fun view(ctx: Context) : View
fun holder(parent: ViewGroup) : CoreViewHolder<T> {
val view = view(parent.context)
return CoreViewHolder(view)
}
}
Need implementation here BindListener'а
.
After all, we perfectly understand why we need an interface BindListener
? We need it to associate our view with a list item.
So, in the method view
we will have access to methods ViewHolderContainer'а
.
Yep, you can do it like this:
abstract class ViewHolderContainer<T> {
abstract fun view(ctx: Context) : View
private var listener: BindListener<T> = BindListener { _, _ -> }
fun onBind(listener: BindListener<T>) {
this.listener = listener
}
fun holder(parent: ViewGroup) : CoreViewHolder<T> {
val view = view(parent.context)
return CoreViewHolder(view, listener)
}
}
Now we can call onBind
when we create a view for the list element and get it when the adapter calls onBindViewHolder
Great! But that is not all (
Kotlin extensions for creating magic!
Let’s create a Kotlin extension like this for RecyclerView
:
fun <T> RecyclerView.adapter(items: List<T>, viewHolderContainer: ViewHolderContainer<T>) {
this.adapter = CoreAdapter(items, viewHolderContainer)
}
Well, let’s test this whole construction on the example of a simple list of characters from the My Little Pony multiseries:
setContentView(list {
vertical()
adapter(
listOf(
"Twilight Sparkle",
"Pinky Pie",
"Fluttershy",
"Rarity",
"Rainbow Dash",
"Apple Jack",
"Starlight Glimmer"
),
object: ViewHolderContainer<String>() {
override fun view(ctx: Context): View {
return text {
fontSize(18f)
colorRes(R.color.black)
padding(dp(24))
layoutParams(recyclerLayoutParams().matchWidth().wrapHeight().build())
onBind { _, ponyName ->
text(ponyName)
}
}
}
}
)
})
Voila!
Here, in addition to the previously written by us adapter
extension, there are a dozen simple Kotlin extensions for creating UI code (see here)
I got a little confused and made such a terrible Kotlin extension:
fun <T> RecyclerView.adapter(items: List<T>, view: (listenItem: (bindListener: BindListener<T>) -> Unit) -> View) {
this.adapter = CoreAdapter(items, object: ViewHolderContainer<T>() {
override fun view(ctx: Context): View {
return view(::onBind)
}
})
}
Now we can do like this:
setContentView(list {
vertical()
adapter(
listOf(
"Twilight Sparkle",
"Pinky Pie",
"Fluttershy",
"Rarity",
"Rainbow Dash",
"Apple Jack",
"Starlight Glimmer"
),
) { onBind ->
text {
fontSize(18f)
colorRes(R.color.black)
padding(dp(24))
layoutParams(recyclerLayoutParams().matchWidth().wrapHeight().build())
onBind { _, ponyName ->
text(ponyName)
}
}
}
})
Result:

Adding DiffUtil.ItemCallback
Let’s use our simple adapter as an example to make another adapter that works with DiffUtil.ItemCallback'ом
:
class CoreAdapter2<T>(
diffUtilItemCallback: DiffUtil.ItemCallback<T>,
private val viewHolderContainer: ViewHolderContainer<T>
) : ListAdapter<T, CoreViewHolder<T>>(diffUtilItemCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoreViewHolder<T> {
return viewHolderContainer.holder(parent)
}
override fun onBindViewHolder(holder: CoreViewHolder<T>, position: Int) {
holder.bind(position, getItem(position))
}
}
Let’s add Kotlin extensions for it:
fun <T> RecyclerView.adapter(diffUtil: DiffUtil.ItemCallback<T>, viewHolderContainer: ViewHolderContainer<T>) : CoreAdapter2<T> {
val adapter = CoreAdapter2(diffUtil, viewHolderContainer)
this.adapter = adapter
return adapter
}
fun <T> RecyclerView.adapter(diffUtil: DiffUtil.ItemCallback<T>, view: (listenItem: (bindListener: BindListener<T>) -> Unit) -> View) : CoreAdapter2<T> {
val adapter = CoreAdapter2(diffUtil, object: ViewHolderContainer<T>() {
override fun view(ctx: Context): View {
return view(::onBind)
}
})
this.adapter = adapter
return adapter
}
Note that here we are returning our adapter in order to then call the well-known submitList
:
setContentView(list {
vertical()
val adapter = adapter(object: DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String) = oldItem == newItem
override fun areContentsTheSame(oldItem: String, newItem: String) = oldItem == newItem
}) { onBind ->
text {
fontSize(18f)
colorRes(R.color.black)
padding(dp(24))
layoutParams(recyclerLayoutParams().matchWidth().wrapHeight().build())
onBind { _, ponyName ->
text(ponyName)
}
}
}
adapter.submitList(listOf(
"Twilight Sparkle",
"Pinky Pie",
"Fluttershy",
"Rarity",
"Rainbow Dash",
"Apple Jack",
"Starlight Glimmer"
))
})
The result is the same.
Final thoughts
I would like to note that such solutions are rarely used now, since there are not many developers in Android who have a passion for writing markup with code without additional libraries or frameworks, especially large and complex projects.
I’ve identified two main problems with my solution:
Poor support (few developers are willing to start writing markup code)
Performance problems and unexpected crashes are possible, because I wrote my solution literally on my knee
And one more note, this solution has incomplete functionality. For example, I didn’t implement support for multiple list item types (viewType
).
Nowadays, there are a lot of ready-made solutions that simplify the creation of adapters for RecyclerView
without unnecessary troubles, crashes and bugs, so my article is most likely for informational purposes, and, to some extent, experimental.
In conclusion, I will say that I am glad for any ideas, even the most unusual and strange ones, so feel free to write 🙂
Good code everyone!
Oh, and a link on repo just in case.