How to use ViewBinding in a RecyclerView.Adapter?

AndroidAndroid Viewbinding

Android Problem Overview


Can I use ViewBindings to replace findViewById in this typical RecyclerView.Adapter initialization code? I can't set a binding val in the object as the ViewHolders are different per cell.

class CardListAdapter(private val cards: LiveData<List<Card>>) : RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {

    class CardViewHolder(val cardView: View) : RecyclerView.ViewHolder(cardView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
        val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CardViewHolder(binding.root)
    }

    override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
        val title = holder.cardView.findViewById<TextView>(R.id.title)
        val description = holder.cardView.findViewById<TextView>(R.id.description)
        val value = holder.cardView.findViewById<TextView>(R.id.value)
        // ...
    }

Android Solutions


Solution 1 - Android

What you need to do is pass the generated binding class object to the holder class constructor. In below example, I have row_payment XML file for RecyclerView item and the generated class is RowPaymentBinding so like this

    class PaymentAdapter(private val paymentList: List<PaymentBean>) : RecyclerView.Adapter<PaymentAdapter.PaymentHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentHolder {
            val itemBinding = RowPaymentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return PaymentHolder(itemBinding)
        }
    
        override fun onBindViewHolder(holder: PaymentHolder, position: Int) {
            val paymentBean: PaymentBean = paymentList[position]
            holder.bind(paymentBean)
        }
    
        override fun getItemCount(): Int = paymentList.size
    
        class PaymentHolder(private val itemBinding: RowPaymentBinding) : RecyclerView.ViewHolder(itemBinding.root) {
            fun bind(paymentBean: PaymentBean) {
                itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber
                itemBinding.tvPaymentAmount.text = paymentBean.totalAmount
            }
        }
    }

Also, make sure you pass the root view to the parent class of Viewholder like this RecyclerView.ViewHolder(itemBinding.root) by accessing the passed binding class object.

Solution 2 - Android

Attach the binding to the ViewHolder instead of the View

class CardViewHolder(val binding: CardBinding) : RecyclerView.ViewHolder(binding.root)

You pass the binding, the binding passes binding.root to RecyclerView.ViewHolder(binding.root)

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
    val binding = CardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    return CardViewHolder(binding)
}

Then access anywhere with:

holder.binding.title

Solution 3 - Android

I wrote a simple and reusable one:

class ViewBindingVH constructor(val binding: ViewBinding) :
    RecyclerView.ViewHolder(binding.root) {
    
    companion object {
        inline fun create(
            parent: ViewGroup,
            crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> ViewBinding
        ) = ViewBindingVH(block(LayoutInflater.from(parent.context), parent, false))
    }
}


class CardAdapter : RecyclerView.Adapter<ViewBindingVH>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH {
        return ViewBindingVH.create(parent, CardBinding::inflate)
    }

    override fun onBindViewHolder(holder: ViewBindingVH, position: Int) {
        (holder.binding as CardBinding).apply {
            //bind model to view
            title.text = "some text"
            descripiton.text = "some text"
        }
    }

}

Solution 4 - Android

You may use view-binding like this :

package com.example.kotlinprogramming.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinprogramming.data.HobbiesData
import com.example.kotlinprogramming.databinding.ItemHobbieBinding

class HobbiesAdapter(var context: Context, var hobbiesList: List<HobbiesData>) :
RecyclerView.Adapter<HobbiesAdapter.HobbiesViewHolder>() {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HobbiesViewHolder {
    val view = ItemHobbieBinding.inflate(LayoutInflater.from(context) , parent,false)
    return HobbiesViewHolder(view)
}

override fun onBindViewHolder(holder: HobbiesViewHolder, position: Int) {
    val hobbie = hobbiesList.get(position)
    holder.viewBinding.tvHobbie.text = hobbie.title
}

inner class HobbiesViewHolder(var viewBinding: ItemHobbieBinding) : RecyclerView.ViewHolder(viewBinding.root) {
}

override fun getItemCount(): Int {
    return hobbiesList.size
}

}

Here is item_hobbies.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_margin="12dp"
android:layout_height="wrap_content"
>
<TextView
    android:id="@+id/tvHobbie"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp"
    android:gravity="center"
    android:textSize="30sp"
    tools:text="Hobbie1"
    />
</androidx.cardview.widget.CardView>

Solution 5 - Android

If you're ok with reflection, I have a much easier way to do this. Just call ViewGroup.toBinding() then you can get the binding object you want. But since we're talk about reflection, remember you have to modify your proguard-rule to make it work even for proguard.

// inside adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    return MainViewHolder(parent.toBinding())
}

// ViewHolder
class MainViewHolder(private val binding: AdapterMainBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(data: String) {
        binding.name.text = data
    }
}

// The magic reflection can reused everywhere.
inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
    return V::class.java.getMethod(
        "inflate",
        LayoutInflater::class.java,
        ViewGroup::class.java,
        Boolean::class.java
    ).invoke(null, LayoutInflater.from(context), this, false) as V
}

I put all this into an open source project you can take a look as well. Not only for Adapter usage but also include Activity and Fragment. And do let me know if you have any comment. Thanks.

https://github.com/Jintin/BindingExtension

Solution 6 - Android

just pass your model calss into xml and set these data into xml this code look fine and add a method where you add these data into binding like you don,t need to fine the id for this

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.setData(listData[position])

}


fun setData(model: ListData) {
        with(binding) {
            data = model
            executePendingBindings()
        }
    }

Solution 7 - Android

You may use data binding like this.

class CardListAdapter(
        private val mActivity: FragmentActivity?
    ) :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    	 private var mCustomLayoutBinding: CustomLayoutBinding? = null
    	 
    	  inner class MyViewHolder(val mBinding: CustomLayoutBinding) :
            RecyclerView.ViewHolder(mBinding.getRoot())
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            if (layoutInflater == null)
                layoutInflater = LayoutInflater.from(parent.context)
    
            var viewHolder: RecyclerView.ViewHolder? = null
            val inflater = LayoutInflater.from(parent.context)
    
       viewHolder = getViewHolder(parent, inflater)
                 return viewHolder!!
            }
    		private fun getViewHolder(
            parent: ViewGroup,
            inflater: LayoutInflater
        ): RecyclerView.ViewHolder {
            mCustomLayoutBinding =
                DataBindingUtil.inflate(inflater, R.layout.custom_layout, parent, false)
            return MyViewHolder(this!!.mAssistanceLogCustomLayoutBinding!!)
        }
          override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            val taskModal = mArrayList.get(position)
    		 holder.mBinding.txtTitle.setText(taskModal.title)
    		  }
    		  override fun getItemCount(): Int {
            return assistanceArrayList.size
        }
    
        override fun getItemId(position: Int): Long {
            return position.toLong()
        }
    
        override fun getItemViewType(position: Int): Int {
            return position
        }
        }
	

Solution 8 - Android

I took what Alan W. did and added Generics to it.

class ViewBindingVH <VB: ViewBinding> constructor(val binding: VB) :
RecyclerView.ViewHolder(binding.root) {

companion object {
    inline fun <VB: ViewBinding> create(
        parent: ViewGroup,
        crossinline block: (inflater: LayoutInflater, container: ViewGroup, attach: Boolean) -> VB
    ) = ViewBindingVH<VB>(block(LayoutInflater.from(parent.context), parent, false))
}}

The implementation is very easy and you avoid the casting on the adapter class:

class PlayerViewHolder : ListAdapter<Rate, ViewBindingVH<ChartItemBinding>>(RateDiff) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewBindingVH<ChartItemBinding> =
    ViewBindingVH.create(parent, ChartItemBinding::inflate)


override fun onBindViewHolder(holder: ViewBindingVH<ChartItemBinding>, position: Int) {
    val item = currentList[position]
    holder.binding.apply {
        
    }
}}

object RateDiff: DiffUtil.ItemCallback<Rate>(){
override fun areContentsTheSame(oldItem: Rate, newItem: Rate): Boolean {
    return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Rate, newItem: Rate): Boolean {
    return oldItem == newItem
}}

Solution 9 - Android

abstract class BaseRecyclerViewAdapter<T : Any, VB : ViewBinding>(
     private var dataList: ArrayList<T>) 
: RecyclerView.Adapter<BaseRecyclerViewAdapter.MyViewViewHolder<VB>>() 
{   
      protected var bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>? = null

   class MyViewViewHolder<VB : ViewBinding>(val viewBinding: VB) :
    RecyclerView.ViewHolder(viewBinding.root) {
        fun <T : Any> bind(
            item: T,
            position: Int,
            bindingInterface: GenericSimpleRecyclerBindingInterface<T, VB>
            ) = bindingInterface.bindData(item, position, viewBinding)
        }

    @SuppressLint("NotifyDataSetChanged")
    fun updateList(list: ArrayList<T>) {
       dataList.clear()
       dataList.addAll(list)
       notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):MyViewViewHolder<VB> {
       val view = inflateView(LayoutInflater.from(parent.context))
       return MyViewViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewViewHolder<VB>, position: Int) {
       val item = dataList[position]
       holder.bind(item, position, bindingInterface!!)
    }

    override fun getItemCount(): Int = dataList.size

    abstract fun inflateView(inflater: LayoutInflater): VB
}

interface GenericSimpleRecyclerBindingInterface<T : Any, VB : ViewBinding> {
     fun bindData(item: T, position: Int, viewBinding: VB)
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionA1mView Question on Stackoverflow
Solution 1 - AndroidSomesh KumarView Answer on Stackoverflow
Solution 2 - AndroidA1mView Answer on Stackoverflow
Solution 3 - AndroidAlan W.View Answer on Stackoverflow
Solution 4 - AndroidSathish GaddeView Answer on Stackoverflow
Solution 5 - AndroidJintinView Answer on Stackoverflow
Solution 6 - AndroidAmit pandeyView Answer on Stackoverflow
Solution 7 - AndroidKomalView Answer on Stackoverflow
Solution 8 - Androiditzik pichhadzeView Answer on Stackoverflow
Solution 9 - AndroidBharat LalwaniView Answer on Stackoverflow