Where in the code should i be using the following lines to work with my widgets:
binding = WorkoutCardBinding.inflate(layoutInflater)
setContentView(binding.root)
I know it should be used in an activiy's onCreate function but I can't seem to get it to work with the below adapter class
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.WorkoutViewHolder>() {
private lateinit var binding: WorkoutCardBinding
inner class WorkoutViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
binding = WorkoutCardBinding.inflate(layoutInflater)
setContentView(binding.root)
val view = LayoutInflater.from(parent.context).inflate(R.layout.workout_card, parent, false)
return WorkoutViewHolder(view)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
holder.itemView.apply {
binding.tvWorkoutCard.text = workouts[position].
}
}
override fun getItemCount(): Int {
return workouts.size
}
val binding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return WorkoutViewHolder(binding)
//Change you onCreateViewHolder completely to this code
You don't have to create a binding variable in the adapter class. To use view binding in recyclerView adapter there are a few things that you've to change.
Firstly, change your viewHolder class constructor to accept viewBinding instead of a view
inner class WorkoutViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.apply {
// create your view here
}
}
Change onCreateViewHolder, to return a viewHolder object using binding.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val workoutCardBinding=
WorkoutCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WorkoutCardBinding(workoutCardBinding)
}
Your complete adapter class will look like this -
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.WorkoutViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val workoutCardBinding =
WorkoutCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return WorkoutCardBinding(workoutCardBinding)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class WorkoutViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.apply {
// create your view here
}
}
}
}
I need to add empty constructor to my Adapter, but i have already primary constructor.
My code:
class RvStatesAdapter(private var stateList: List<State>): RecyclerView.Adapter<RvStatesAdapter.MyViewHolder>() {
inner class MyViewHolder(val binding: RvStateListBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(RvStateListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return stateList.size
}
I have tried to use costructor(): this() but in this case i dont understand what i need to put in this() brackets
You can make Adapter constructor without params by instead of passing list in primary constructor use function to submit list in adapter.
class RvStatesAdapter(): RecyclerView.Adapter<RvStatesAdapter.MyViewHolder>() {
private val stateList: ArrayList<State> = ArrayList<State>()
/**
* submit list to recycler view adapter for populating items
*/
fun submitList(list: List<State>) {
stateList.addAll(list)
}
inner class MyViewHolder(val binding: RvStateListBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
RvStateListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return stateList.size
}
}
Submit list using adapter instance
val list = emptyList<State>()
val rvStatesAdapter = RvStatesAdapter()
// init recyclerview properties
rvStatesAdapter.submitList(list)
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)
// ...
}
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
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"
}
}
}
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>
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
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()
}
}
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
}}
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)
}
I am using a CarouselView library to show images in a RecyclerView.
I have created a custom view which uses this PhotoView library, to make the images zoomable.
I need to implement a custom ViewGoup class which is explained here. I don't know where to change my RecyclerView Adapter, to use my CustomViewGroup instead of the default ViewGroup.
My ViewGroup class looks like shown
class MyViewGroup(context: Context) : ViewGroup(context) {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
try {
return super.onInterceptTouchEvent(ev)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
return false
}
}
}
And this is my recyclerview adapter
class MyAdapter(
var dataset: MutableList<Upload> = mutableListOf(),
var context: Context?
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
class ViewHolder(val layout: RelativeLayout) : RecyclerView.ViewHolder(layout)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cont = LayoutInflater.from(parent.context)
.inflate(R.layout.my_item, parent, false) as RelativeLayout
return ViewHolder(cont)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {}
override fun getItemCount(): Int {
return dataset.size
}
}
My recycler view has two types of item view. One type of them has MPAndroidChart in it. I need to do some chart view configuration that cannot be done in XML. How can I do it given that I am using RecyclerView data binding with a single base view holder (as recommended by George Mount) ?
open class BaseViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(obj: Any) {
binding.setVariable(BR.obj, obj)
binding.executePendingBindings()
}
}
abstract class BaseAdapter : RecyclerView.Adapter<BaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = DataBindingUtil.inflate<ViewDataBinding>(layoutInflater, viewType, parent, false)
return BaseViewHolder(binding)
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
val obj = getObjForPosition(position)
holder.bind(obj)
}
override fun getItemViewType(position: Int): Int {
return getLayoutIdForPosition(position)
}
protected abstract fun getObjForPosition(position: Int): Any
protected abstract fun getLayoutIdForPosition(position: Int): Int
}
You can still access
holder.itemView.myChartViewId.doSomeStuff()
on the onBindViewHolder() call.
You can also implement a function to "initialize" your charts in your view holder like this:
open class BaseViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(obj: Any) {
binding.setVariable(BR.obj, obj)
binding.executePendingBindings()
}
fun initCharts() {
if (itemView.myChartViewId == null) return
itemView.myChartViewId.doSomwStuff()
}
}
and call it whenever you need.