paint-brush
3 Quick Ways to Optimize RecyclerViewby@azamatnurkhojayev
958 reads
958 reads

3 Quick Ways to Optimize RecyclerView

by Azamat NurkhojayevMay 14th, 2024
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

In this article, I would like to look at quick ways to optimize when using RecyclerView. RecyclerView is a user interface component that is, an element that can be added to the interface to display a list conveniently.
featured image - 3 Quick Ways to Optimize RecyclerView
Azamat Nurkhojayev HackerNoon profile picture


Hi everyone!


In this article, I would like to look at quick ways to optimize when using RecyclerView.


RecyclerView is a user interface component that is, an element that can be added to the interface to display a list conveniently. It is built into the code and already contains tools for displaying, animating, and optimizing the list, and also supports customization settings.

The Main Components of RecyclerView

For RecyclerView to work correctly, you must implement the following components:

  • RecyclerView, which must be added to the layout of our Activity;
  • Adapter, which contains, processes, and associates data with the list;
  • ListAdapter, updated Adapter and accepts DiffUtil in the constructor;
  • ViewHolder, which serves to optimize resources and is a kind of container for all elements included in the list;
  • DiffUtil, which is used to optimize the list.


I won’t go into how to implement it from scratch, you can find it at the link.

First Optimization of ViewHolder.

Remove access to resources and casting in ViewHolder when binding.


For example, take the code below:

data class TrackingUiModel(  
    val id: Long,  
    val title: String,  
    @ColorRes  
    val color: Int,  
    val eventId: Long,  
    val state: TrackingState,  
    val startTime: Long,  
    val endTime: Long,  
    val countTime: Long,  
    val formattedTime: String,  
)


class TrackingViewHolder(itemView: View): ViewHolder(itemView) {  
  
    private val binding : TrackingItemBinding by viewBinding()  
  
    @SuppressLint("SetTextI18n")  
    fun bind(data: TrackingUiModel) {  
        val stateText = when (data.state) {  
            TrackingState.START -> itemView.context.getString(R.string.in_progress)  
            TrackingState.PAUSE ->  itemView.context.getString(R.string.pause)  
            else -> {  
                ""  
            }  
        }  
        binding.eventId.text = data.eventId.toString()
        binding.eventTitle.text = "${data.title} $stateText"  
        binding.eventColor.setBackgroundColor(ContextCompat.getColor(itemView.context, data.color))  
        binding.countTextView.text = data.formattedTime  
    }
}


Each time the bind method is called, the getString and getColor calls will be called, and toString will also be called for string casting. This way we consume more memory. And you need to make sure that the bind method is executed quickly, and you should strive to ensure that the ViewHolder task is only displaying list items. For optimizations, it is better to get the string eventId, stateText and color in the TrackingUiModel object.


The optimized code now looks like this:

data class TrackingUiModel(  
    val id: Long,  
    val title: String,  
    val color: Int,  
    val eventId: String,  
    val state: TrackingState,  
    val stateText: String,  
    val startTime: Long,  
    val endTime: Long,  
    val countTime: Long,  
    val formattedTime: String,  
)

class TrackingViewHolder(itemView: View): ViewHolder(itemView) {  
  
    private val binding : TrackingItemBinding by viewBinding()  
    
    fun bind(data: TrackingUiModel) {  
        binding.eventId.text = data.eventId  
        binding.eventTitle.text = "${data.title} ${data.stateText}"  
        binding.eventColor.setBackgroundColor(data.color)  
        binding.countTextView.text = data.formattedTime  
    }
}


We have taken everything beyond the ViewHolder, everything we need and ready-made will be received in TrackingUiModel.

Second, We Will Use ListAdapter and DiffUtil.

If you are using RecyclerView.Adapter then you should switch to ListAdapter, and use DiffUtil along with it.


For example, take the code below:

class TrackingAdapter: RecyclerView.Adapter<TrackingViewHolder>(){  
  
    private val mItems = mutableListOf<TrackingUiModel>() 
     
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackingViewHolder {  
        return TrackingViewHolder(  
            LayoutInflater.from(parent.context)  
            .inflate(R.layout.tracking_item, parent, false))  
    }  
  
    override fun getItemCount(): Int = mItems.size  
  
    override fun onBindViewHolder(holder: TrackingViewHolder, position: Int) {  
        holder.bind(mItems[position])  
    }  
  
    fun setItems(items: List<TrackingUiModel>){  
        mItems.clear()  
        mItems.addAll(items)  
        notifyDataSetChanged()  
    }  
}


When adding list elements, calling the setItems method clears the list, adds list elements, and finally calls notifyDataSetChanged.


Let's imagine that we change or update the list, and the setItems method will be called each time; this will be quite resource-consuming. For this reason, it is better to use ListAdapter and DiffUtil.


Below is a modified TrackingAdapter that implements ListAdapter and uses DiffUtil; in my example, it is the TrackingDiffCallback class.


To add a list, we use the trackingAdapter.submitList method, and under the hood, the adapter will do all the work of updating the list for us.


class TrackingAdapter: ListAdapter<TrackingUiModel, TrackingViewHolder>(TrackingDiffCallback()){  

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackingViewHolder {  
        return TrackingViewHolder(  
            LayoutInflater.from(parent.context)  
            .inflate(R.layout.tracking_item, parent, false))  
    }  
  
    override fun onBindViewHolder(holder: TrackingViewHolder, position: Int) {  
        holder.bind(getItem(position))  
    }  
}


class TrackingDiffCallback: DiffUtil.ItemCallback<TrackingUiModel>() {  
  
    override fun areItemsTheSame(oldItem: TrackingUiModel, newItem: TrackingUiModel): Boolean {  
        return oldItem.id == newItem.id  
    }  
  
    override fun areContentsTheSame(oldItem: TrackingUiModel, newItem: TrackingUiModel): Boolean {  
        return oldItem == newItem  
    }  
}


Third, Use Payload.

There are such cases as adding list items to favorites, changing an image, or changing some other view of a list item. When changes occur in all these cases, the bind method is called again in the ViewHolder, and we visually notice the highlight of the element. To prevent this from happening, payload comes to us for help.


To use payload, let's make a change at ViewHolder, DiffUtil, and Adapter.


In my case, I will make the following changes.


In TrackingViewHolder, we add a bind method with the data that needs to be changed.

fun bind(data: TrackingUiModel, newTime: String) {  
    binding.eventId.text = data.eventId  
    binding.eventTitle.text = "${data.title} ${data.stateText}"  
    binding.eventColor.setBackgroundColor(data.color)  
    binding.countTextView.text = newTime  
}


In TrackingDiffCallback, we override the getChangePayload method and compare the field being changed. In my case, it's formattedTime.

override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? {  
    if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime  
    return super.getChangePayload(oldItem, newItem)  
}


In TrackingListAdapter, we override the onBindViewHolder method with payloads. We check whether the payload is empty or not, if it is not empty, then we get the first element and call the bind method for the payload.

override fun onBindViewHolder(holder: TrackingViewHolder, position: Int, payloads: MutableList<Any>) {  
    if (payloads.isEmpty()) {  
        super.onBindViewHolder(holder, position, payloads)  
    } else {  
	    val newTime = payloads.firstOrNull() as? String ?: ""
        holder.bind(getItem(position), newTime)  
    }  
}



Without payload


With payload



Reference links: