Common Mistake Developers Make When Building RecyclerViews and How to Fix It Using “ViewRepresentation” Class
When creating RecyclerView lists, we usually encounter list items that have the same ViewHolder but the values inside it are subject to different conditions.
Let’s say we’re building a list that displays invoices in a card view. Each item in the list has these conditions:
For unpaid invoice:
- Displays “Unpaid” label at the top right corner of the card view
- Displays “Pay” button at the bottom of the card view
- Displays due date in “dd-MM-yyyy” format
For paid invoice:
- Displays “Paid” label at the top right corner of the card view
- Displays “View” button at the bottom of the card view
- Displays due date in “dd-MM-yyyy” format
Let’s use this minimal example just for this article.
Common Mistake
The common mistake I usually notice with some developers is they handle these conditions inside the adapter’s onBindViewHolder()
method like this:
private val dateFormatter = SimpleDateFormat("dd-MM-yyyy")
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item: Invoice = getItem(position) if (item.status == PAID) {
holder.status.text = getString(R.string.paid)
holder.button.text = getString(R.string.view)
} else {
holder.status.text = getString(R.string.unpaid)
holder.button.text = getString(R.string.pay)
}
holder.dueDate.text = dateFormatter.format(invoice.dueDate)
}
Surely, this is fine for displaying short lists and for lists that only have those three conditions mentioned above. But, what if we’re displaying hundreds/thousands of items in the list with more conditions?
This would cause onBindViewHolder()
to repetitively run those conditions to bind the correct values to the views and the pain point is… poor scrolling performance.
My take on this
As much as possible, the onBindViewHolder’s job should solely be for binding items to the list alone. All the conditions behind the values should be calculated beforehand.
With this in mind, I have come up with this solution.
The “ViewRepresentation” Class
ViewRepresentation is a class that is ready to be bound to the RecyclerView.
This class should already contain the correct values so that the onBindViewHolder()
's job is solely for binding those values to the list.
So, how should this look like using our example?
So what is this class doing? Let’s break them down below:
- It has 3 fields
statusText
,buttonText
, anddueDateText
. These three fields should already contain the correct values to be displayed in the list. fromInvoice()
utility method. This method should transformInvoice
into its ViewRepresentation equivalent. This is where we run all the necessary conditions and set them ready for the list.
Updated onBindViewHolder() method
Now that we already have our ViewRepresentation class for our Invoice list. Our onBindViewHolder()
method should now look like this.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item: InvoiceViewRepresentation = getItem(position) holder.status.text = item.statusText
holder.button.text = item.buttonText
holder.dueDate.text = item.dueDateText
}
We have met our goal to make onBindViewHolder()’s only job is to bind the values to the view. That’s how it should be!
We have successfully eliminated repetitively doing the following while scrolling through the list:
- Running the
if (status == PAID)
condition - Calling
dateFormatter.format()
And, this should result in a better performant RecyclerView!
And that’s basically it!
I have been using this approach for quite a while now and have no problems with it so far.
Also, it is very convenient especially when the “Invoice” is bound into different screens in your app. You can just reuse the same InvoiceViewRepresentation class and ensure its values are calculated consistently throughout different screens.
I hope this can help someone build better performant lists.
Thank you for reading!