ListAdapter: Extension to RecyclerView.Adapter

ListAdapter was added in Recyclerview library version 27.1.0.

It is RecyclerView.Adapter base class for presenting List data in a RecyclerView, including computing diffs between Lists on a background thread.

Before jump into ListAdapter example, Let us see problems with below implementation.

class SampleAdapter : RecyclerView.Adapter() {
    var sampleList: List = Collections.emptyList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
        val binding = SampleListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CustomViewHolder(binding)
    }

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        holder.bind(sampleList.get(position))
    }

    fun updateList(list: List) {
        sampleList = list
        notifyDataSetChanged()
    }

    override fun getItemCount(): Int {
        return sampleList.size
    }

    class CustomViewHolder(private val binding: SampleListItemBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(sample: Sample) {
            binding.apply {
                nameItem.text = sample.name
                ageItem.text = sample.age.toString()

            }
        }
    }

}

With above code, set data to adapter by adding a method like updateList() and then in the method we assign a new list and then we call notifyDataSetChanged() to update RecyclerView.

The method notifyDataSetChanged() refreshes whole list and onBindViewHolder() is called for all of the items in the list even if the item doesn’t have any change at all.

To solve above problem, We can use DiffUtil. It calculates difference between two lists(old and new) and provide the updated list to update the list.

 

class SampleDiffCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(oldItem: Sample, newItem: Sample) =
                oldItem.age == newItem.age

        override fun areContentsTheSame(oldItem: Sample, newItem: Sample) =
                oldItem == newItem
    }

The problem is that DiffUtil calculates the difference on main thread which leads to performance issues when the adapter holds larger amount of data in the list. To avoid that, we need to run DiffUtil on background thread and pass results to the main thread.

ListAdapter provides easy way to solve all the above problems.

ListAdapter provides a method submitList(List) to provide new or modified data to RecyclerView.Adapter and handles all the diffs computation. So we don’t need to do much of setup here. All we need to do is provide a instance of DiffUtil.ItemCallback that determines whether the item is changed or not.

Here is the ListAdapter implementation

class SampleAdapter : ListAdapter<Sample, CustomViewHolder>(SampleItemDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, position: Int): CustomViewHolder {
        val binding = SampleListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CustomViewHolder(binding)

    }

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }

    class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
    
        fun bindTo(user:User){
           // bind views with data
        }  
    }

    class SampleItemDiffCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(oldItem: Sample, newItem: Sample): Boolean = oldItem == newItem

        override fun areContentsTheSame(oldItem: Sample, newItem: Sample): Boolean = oldItem == newItem

    }

}

Diffs will be calculated on background thread and adapter will be notified with the results on main thread.

We can update the list to adapter using submitList() method from Activity/Fragment

adapter = SampleAdapter()
listview.adapter = adapter

sampleListLiveData.observe(this, Observer {list->
    adapter.submitList(list)
})

References: