Search code examples
androidkotlinandroid-recyclerviewandroid-databinding

How to handle clicks on a view inside of a RecyclerView list item. Using data binding and kotlin


All of the resources I've found are either in Java or only cover clicking the list item itself not a view inside of the list item. I believe I am supposed to set this up inside the ViewHolder class but I am not sure how. This is my ViewHolder class.

class ItemListAdapter(val clickListener : ItemListListener) : ListAdapter<Item , ItemListAdapter.ItemViewHolder>(DiffCallback) {

class ItemViewHolder(private var binding : ItemListItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(item : Item, clickListener : ItemListListener) {

        binding.item = item
        binding.clickListener = clickListener
        
        // button that inside listItem that I want to respond to clicks
        // binding.addOneButton ???????
        

        binding.executePendingBindings()
    }
}

And my listener

class ItemListListener(val clickListener : (singleItem : Item) -> Unit) {

fun onClick(item : Item){

    clickListener(item)


}

}

Other method calls in case they are relevant

override fun onBindViewHolder(holder : ItemViewHolder , position : Int) {
    val item = getItem(position)
    holder.bind(item, clickListener)
}

companion object DiffCallback : DiffUtil.ItemCallback<Item>() {
    override fun areItemsTheSame(oldItem : Item , newItem : Item) : Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem : Item , newItem : Item) : Boolean {
        return oldItem.itemId == newItem.itemId
    }
}

override fun onCreateViewHolder(
    parent : ViewGroup ,
    viewType : Int
) : ItemViewHolder {
    return ItemViewHolder(ItemListItemBinding.inflate(LayoutInflater.from(parent.context)))
}

Solution

  • First of All, Listener is supposed to be an Interface.

    interface ItemListListener {
        fun onClick(item: Item,actionType:String) 
    }
    

    Then in Activity/Fragment class,implement this interface. Initialize adapter.

    recyclerView.adapter  = ItemListAdapter(this)
    

    You can update your ItemViewHolder class with following code. Make it inner class, Then you'll not need to pass clickListener instance. And click listener should be registered in init method of View Holder class.

     inner class ItemViewHolder(private val binding : ItemListItemBinding) :
                RecyclerView.ViewHolder(binding.root) {
    
                  init {
                        binding.addOneButton.setOnClickListener {
                          clickListener.onClick(getItem(adapterPosition),"button")
                     }
                      binding.root.setOnClickListener {
                          clickListener.onClick(getItem(adapterPosition),"root")
                     }
                 }
                fun bind(item : Item) {
                    binding.item = item
                    binding.executePendingBindings()
                }
            }
    

    ** Update **

    After some research I got answer to your question. Here's the solution. Instead of an interface, You can pass Higher Order function to Adapter class.

    class ItemListAdapter(val clickListener : (Item,String) -> Unit ):ListAdapter<Item , ItemListAdapter.ItemViewHolder>(DiffCallback)
    

    In View Holder class

    binding.addOneButton.setOnClickListener {
                          clickListener(getItem(adapterPosition),"button")
                     }
                      binding.root.setOnClickListener {
                          clickListener(getItem(adapterPosition),"root")
                     }
    

    In Activity/Fragment

    recyclerview.adapter = ItemListAdapter({item, action->
    })
    

    You can do it with Binding as well.