How to use ViewBinding in a RecyclerView.Adapter?
Matthew Barrera
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) // ... } 4 9 Answers
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.
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 4 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" } }
} 8 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=""
xmlns: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> 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.
3just 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() } } 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 } } 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
}} 1 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)
}