Kotlin ListAdapter reset RecyclerView after submitList

Viewed 888

I'm working on android apps using MVVM, and Data Binding. I'm using ListAdapter for my RecyclerView Adapter. The case is, when I submit new data to the adapter using submitList, it reset RecyclerView scroll position. It blink at first and just reset it's position to the top.

My Binding Adapter

@BindingAdapter("listTemplate", "hirarki")
fun bindListTemplate(recyclerView: RecyclerView, data: List<Template>?, hirarki: Int) {
        var adapter = recyclerView.adapter as TemplateChiefAdapter
        adapter.submitList(data)


}

TemplateFragment where I resubmit my data

 navController.currentBackStackEntry?.savedStateHandle?.getLiveData<Boolean>("shouldUpdate")
            ?.observe(viewLifecycleOwner, {
                if (it) {
                    viewModel.fetchdata()
                    navController.currentBackStackEntry?.savedStateHandle?.remove<Boolean>("shouldUpdate")
                }
            })

This piece of code will update LiveData in my ViewModel, so the DataBinding will detect its change and re-submitList the data to the adapter

My List Adapter

class TemplateChiefAdapter(val onClickListener: OnClickListener) : ListAdapter<Template, TemplateChiefAdapter.TemplateChiefViewHolder>(DiffCallback) {

    class TemplateChiefViewHolder(private var binding: ItemTemplateChiefBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(template: Template) {
            binding.template = template
            binding.executePendingBindings()
        }

    }

    companion object DiffCallback : DiffUtil.ItemCallback<Template>() {
        override fun areItemsTheSame(oldItem: Template, newItem: Template): Boolean {
            return oldItem === newItem
        }

        override fun areContentsTheSame(oldItem: Template, newItem: Template): Boolean {
            return oldItem.id_template == newItem.id_template
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TemplateChiefViewHolder {
        return TemplateChiefViewHolder(ItemTemplateChiefBinding.inflate(LayoutInflater.from(parent.context)))
    }

    override fun onBindViewHolder(holder: TemplateChiefViewHolder, position: Int) {
        val template = getItem(position)
        holder.itemView.setOnClickListener {
            onClickListener.onClick(template)
        }
        holder.bind(template)
    }

    class OnClickListener(val listener: (template: Template) -> Unit) {
        fun onClick(template: Template) = listener(template)
    }
}

How can I keep the recycler scroll position after submitList called?

1 Answers

I didn't examine in ultra detail all your code, but the DiffUtil Callback caught my attention.

areItemsTheSame is an optimization from the DiffUtil class to determine if the items changed position. If the didn't, then the contents can be checked, and re-bound to their new data if it changed. If the positions changed, then the item may need to be animated elsewhere or well.. as you can imagine it becomes more complicated from there.

The idea of that method is to compare if the items are the same or not, not to compare the entire item. I would use an id (or anything that can help you identify uniqueness in your items). You are using the === operator and I don't know the rest of your architecture, but comparing by reference may not be accurate if, for instance, your data layer transforms and copies these objects around (something you can't/shouldn't tell/care for in your adapter).

For instance, instead of

return oldItem === newItem

You could do

return oldItem.someId === newItem.someId

This would ensure that even if your items are the same but were copied/recreated/etc., you'd still identify them as such despite them being a different reference.

Then, in areContentsTheSame you are expected to check all the contents that you consider instrumental in deciding if onBind must be called on your specific viewHolder because the contents are different. So I would have expected something more like:

oldItem.something == newItem.something
   && oldItem.xxx == newItem.xxx
   && oldItem.yyy == newItem.yyy

(but maybe with DataBinding you don't need this, I wouldn't know).

All that being said, I have 0.1 experience with DataBinding (and personally for me that was enough), so if this is related in anyway how the data binding library behaves, I can't help you any more. :/

From a RecyclerView's point of view, the rest of the code looks adequate.

Related