Revolut app
Our application has many types of pictures to display – there are lists of transactions with different icons, lists of cards, Lottie animations, gifs. I’ll show you how we work with pictures using the example of a list of transactions.
Our list of transactions has several dozen types of cells. For example, we’ll take five:
Different types of transactions where we show the picture
In each case, the picture is taken from a separate source or generated.
How the standard way of displaying pictures works
Let’s create an adapter for such a list.
class TransactionsAdapter: RecyclerView.Adapter () {
private var items = mutableListOf ()
override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from (parent.context)
.inflate (R.layout.view_transaction, parent, false)
return ViewHolder (view)
}
override fun onBindViewHolder (holder: ViewHandler, position: Int) = Unit
override fun getItemCount () = items.size
class ViewHolder (itemView: View): RecyclerView.ViewHolder (itemView) {
private val imageView: ImageView = itemView.findViewById (R.id.image)
}
}
This is what the standard adapter template for RecyclerView would look like. Let’s implement value binding:
override fun onBindViewHolder (holder: ViewHandler, position: Int) {
val transaction = items [position]
when {
transaction.isContactWithAvatar () -> {
// Load and display the avatar
}
! transaction.isContactWithAvatar () -> {
// Display avatar
}
transaction.isMerchantWithAvatar () -> {
// Load and display the avatar
}
! transaction.isMerchantWithAvatar () -> {
// Load image from source
}
}
}
A footcloth of conditions appears, because inside the adapter we build separate logic for each type of transaction. You can complicate and use your ViewType for each source. Moreover, the adapter contract pushes for this:
override fun getItemViewType (position: Int): Int {
val transaction = items [position]
return when {
transaction.isContactWithAvatar () -> VIEW_TYPE_CONTACT_WITH_AVATAR
! transaction.isContactWithAvatar () -> VIEW_TYPE_CONTACT_WITHOUT_AVATAR
transaction.isMerchantWithAvatar () -> VIEW_TYPE_MERCHANT_WITH_AVATAR
! transaction.isMerchantWithAvatar () -> VIEW_TYPE_MERCHANT_WITHOUT_AVATAR
else -> VIEW_TYPE_UNKNOWN
}
}
Considering that in our case there can be several dozen types of transactions, the standard way of implementing the adapter is not suitable.
How to improve the adapter
We can distinguish two main approaches to extension – ViewType or delegates. The rest are not specifically mentioned: in essence, they will be similar to the second approach.
The first option – ViewType – can be used when the application is simple, that is, it contains one simple list and, for example, a couple of screens. This method is not suitable for us, because such adapters cannot be reused. If we expand the adapter by adding new ViewType, the adapter will grow uncontrollably. In addition, we will have to create our own adapters for each screen.