2

I use a binding for a ListAdapter with the definition of a DiffUtil.ItemCallback. When deleting items (at least 2) I have an IndexOutOfBoundsException. The update of the list works (the number of elements is indeed N-1 after deletion) but not the position of the item, which is kept is the call. The exception's therefore thrown when calling getItem(position) (in the onBindViewHolder). NB: A log of getItemCount() just before the getItem(position) shows that the list contains N-1 elements. I created a small repo: https://github.com/jeremy-giles/DiffListAdapterTest (with a same configuration to my project) which reproduces the problem.

ItemAdapter class

class ItemAdapter(
    var listener: ListAdapterListener) : DataBindingAdapter<Item>(DiffCallback()) {

    class DiffCallback : DiffUtil.ItemCallback<Item>() {

        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
           return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
           return oldItem == newItem
        }
    }

    override fun getItemViewType(position: Int) = R.layout.recycler_item

    override fun onBindViewHolder(holder: DataBindingViewHolder<Item>, position: Int) {
        super.onBindViewHolder(holder, position)

        holder.itemView.tv_position.text = "Pos: $position"

        holder.itemView.setOnLongClickListener {
            Timber.d("List item count: ${itemCount}, position: $position")
            listener.onLongViewClick(getItem(position), position)
        }
    }

    interface ListAdapterListener {
        fun onLongViewClick(item: Item, position: Int) : Boolean
    }
}

BindingUtils classes

abstract class DataBindingAdapter<T>(diffCallback: DiffUtil.ItemCallback<T>) :
    ListAdapter<T, DataBindingViewHolder<T>>(diffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder<T> {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = DataBindingUtil.inflate<ViewDataBinding>(layoutInflater, viewType, parent, false)

        return DataBindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: DataBindingViewHolder<T>, position: Int) {
        holder.bind(getItem(position))
    }
}

class DataBindingViewHolder<T>(private val binding: ViewDataBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(item: T) {
        binding.setVariable(BR.item, item)
        binding.executePendingBindings()
    }
}

And in my MainActivity class I use a LiveData to update the recyclerView

itemViewModel.getListObserver().observe(this, Observer {
        Timber.d("List Observer, items count ${it.size}")
        itemAdapter.submitList(it.toList())
    })

2 Answers 2

7

In your onBindViewHolder update usage of 'position' to 'holder.getAdapterPosition()':

override fun onBindViewHolder(holder: DataBindingViewHolder<Item>, position: Int) {
        super.onBindViewHolder(holder, position)

        holder.itemView.tv_position.text = "Pos: $position"

        holder.itemView.setOnLongClickListener {
            Timber.d("List item count: ${itemCount}, position: $position")
            listener.onLongViewClick(getItem(holder.getAdapterPosition()), holder.getAdapterPosition())
        }
    }
Sign up to request clarification or add additional context in comments.

Comments

0

Currently holder.getAdapterPosition() is deprecated. As per documentation, you might want to use either one of the following two methods.

If you are calling this in the context of an Adapter, you probably want to call getBindingAdapterPosition() or if you want the position as RecyclerView sees it, you should call getAbsoluteAdapterPosition().

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.