A Better Way to Handle Click Action in a RecyclerVIew Item.
There are various approaches to set the OnClickListener to a RecyclerView item. Here is one of the approaches that I consider to be the best practice.
Setting the OnClickListener on the list item is a very common task in Android development. As an Android developer, you should be well-familiar with it. However, I have seen various implementations from different developers. Back in the day when I still used Java, I would need an interface that specified the click listener’s behavior to handle the click action in the Fragment or Activity with the data from a specific item of the RecyclerView. The interface might look like this:
public interface OnItemClickListener {
void onItemClick(Model model);
}
Now in 2020, we have Kotlin and we can just use Lambda to achieve the same behavior as having the interface class in Java.
Let's begin with the RecyclerAdapter. In our Recycler Adapter, the constructor takes a Lambda as the constructor param. This Lambda acts as the listener with the model to be rendered in our Fragment/Activity.
class AwesomeAdapter(
private val onItemClicked: (Model) -> Unit
) : RecyclerView.Adapter<ViewHolder> {
var data: List<Model> = ArrayList(0)
set(value) {
field = value
notifyDataSetChanged()
}
}
Here comes the part where I have seen people implement it differently. Where do you think we should set the click listener to each item of the RecyclerView; onBindViewHolder or onCreateViewHolder?
Setting the click listener in onBindViewHolder iseasy because the adapter is holding the list and we can access data itemsby their position in onBindViewHolder directly:
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val item = data[position]
holder.bind(item)
holder.itemView.setOnClickListener { onItemClicked(item) }
}
}
However, in RecyclerView the onBindViewHolder gets called every time the ViewHolder is bound and the setOnClickListener will be triggered too. Therefore, setting a click listener in onCreateViewHolder which invokes only when a ViewHolder gets created ispreferable.
Hereis a diagram of the implementation.
In the Adapter class, onCreateViewHolder has no reference to the position, so we need to refer to RecyclerView item's position by the adapterPosition field in ViewHolder class. As you can see, we pass another Lambda into the ViewHolder class so that we can refer to an adapter position data in the Adapter class.
On Fragment/Activity
This is how we initialize the adapter with a click listener.
val adapter = AlbumAdapter {model ->
//The click action you want to perform.
}
On Adapter class
class ArtistAdapter(
private val onItemClicked: (Model) -> Unit
) : RecyclerView.Adapter<ArtistViewHolder>() {
var data: List<Model> = ArrayList(0)
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, type: Int): ArtistViewHolder {
val viewHolder = LayoutInflater.from(parent.context).inflate(R.layout.viewholder_awesome, parent, false)
return AwesomeViewHolder(viewHolder) {
onItemClick(values[it])
}
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(viewHolder: ArtistViewHolder, position: Int) {
viewHolder.bind(data[position])
}
}
ViewHolder Class
class ArtistViewHolder(
view: View,
onItemClicked: (Int) -> Unit
) : RecyclerView.ViewHolder(view) {
init {
itemView.setOnClickListener {
onItemClicked(adapterPosition)
}
}
fun bind(model: Model) {
//bind data
}
}
Conclusion
I think setting a click listener of the RecyclerView item in onCreateViewHolder is a best practice since it reduces the function call significantly compared to doing it in onBindViewHolder. However, you might wonder if it is really necessary because setting a click listener is just a small operation and won't cause much of the performance issue anyway. That is true, but let's consider the situation where each item of the RecyclerView is getting more complex like having multiple click actions or even including a long click event. In this case, implementing the approach, I mentioned in this article would be more preferable.
looks great.. really like your ideas
was there a link to a sample implementation?
just trying to implement your idea now, and having a couple of little glitches.
thank you,
K
Ben
May 17th, 2021
You could try checking this file on my repo. It implements a click action the same way as mentioned in the article.
https://github.com/BenBoonya/android-pokemon-info/blob/master/app/src/main/java/com/benboonya/pokemoninfo/common/ui/PagedItemListAdapter.kt
Alex M
May 22nd, 2021
I like this solution so much. Thanks
otong
September 5th, 2021
im confused with ur code sir, any github?
Dennis
October 20th, 2021
This is how Google does it in the sunflower sample. But when doing this and when you're also using a CardView as the root layout element for the viewholder, multiple ripple animations will be appearing on multiple items after a single click because the clicklistener is reused for the recycled viewholders.
Senna
October 27th, 2021
this is a really good article thank you
raffly
January 9th, 2022
how about other listeners? like onCheckedChangeListener, do you have any idea?
Anjali
January 12th, 2022
How do you handle click event in Nested RecyclerView?
harr
March 13th, 2022
This is very helpful! But when I try and define both onClick and onLongClick lambdas in my adapter class constructor, I can't seem to define both in my fragment/activity class. Can anyone link to an example of defining multiple click behaviors?
Ashton
January 31st, 2023
Tried, not useful. Just write setOnClick in the bindViewHolder is fine. Even for super complex view, is better to write in the bindViewHolder to make the code clear and neat.
Jason
March 18th, 2023
Line 14 of ArtistAdapter reads:
onItemClick(values[it])
But shouldn't it read:
onItemClick(data[it])
was there a link to a sample implementation?
just trying to implement your idea now, and having a couple of little glitches.
thank you,
K
https://github.com/BenBoonya/android-pokemon-info/blob/master/app/src/main/java/com/benboonya/pokemoninfo/common/ui/PagedItemListAdapter.kt
onItemClick(values[it])
But shouldn't it read:
onItemClick(data[it])