How to Fix IndexOutOfBoundsException in RecyclerView Adapter's onRowMoved Function?

53 Views Asked by At

I'm facing an issue with the implementation of the onRowMoved function within my RecyclerView adapter.

This function is responsible for handling item movements in the RecyclerView when items are dragged and dropped.

However, I'm encountering a java.lang.IndexOutOfBoundsException.


My code is:


val touchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {

    ...

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        return imagesAdapter?.onRowMoved(viewHolder, target) ?: false
    }

})

fun onRowMoved(
    fromViewHolder: RecyclerView.ViewHolder,
    toViewHolder: RecyclerView.ViewHolder
): Boolean {
    val fromPosition = fromViewHolder.bindingAdapterPosition
    val toPosition = toViewHolder.bindingAdapterPosition
    val imagesSize = this.images.size

    if (fromPosition < imagesSize && toPosition < imagesSize) {
        if (fromPosition < toPosition) {
            for (i in fromPosition until toPosition) {
                Collections.swap(this.images, i, i + 1)
            }
            notifyItemMoved(fromPosition, toPosition)
        } else {
            for (i in fromPosition downTo toPosition + 1) {
                Collections.swap(this.images, i, i - 1)
            }
            notifyItemMoved(toPosition, fromPosition)
        }

        Handler().postDelayed({
            notifyDataSetChanged()
        }, 1000)
        return true
    }
    return false
}

Crash logs are following:

Fatal Exception: java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 8
       at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
       at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
       at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
       at java.util.Objects.checkIndex(Objects.java:359)
       at java.util.ArrayList.get(ArrayList.java:434)
       at java.util.Collections.swap(Collections.java:548)
       at com....newProduct.NewProductImagesAdapter.onRowMoved(NewProductImagesAdapter.kt:208)
       at com....newProduct.NewProduct$createImagesAdapter$touchHelper$1.onMove(NewProduct.kt:927)
       at androidx.recyclerview.widget.ItemTouchHelper.moveIfNecessary(ItemTouchHelper.java:891)
       at androidx.recyclerview.widget.ItemTouchHelper$2.onTouchEvent(ItemTouchHelper.java:390)
       at androidx.recyclerview.widget.RecyclerView.dispatchToOnItemTouchListeners(RecyclerView.java:3515)
       at androidx.recyclerview.widget.RecyclerView.onTouchEvent(RecyclerView.java:3713)
       at android.view.View.dispatchTouchEvent(View.java:14879)
1

There are 1 best solutions below

0
rv_5143 On BEST ANSWER

It looks like you're using bindingAdapterPosition to get the adapter position of the ViewHolders, but keep in mind that bindingAdapterPosition can return NO_POSITION if the ViewHolder is not currently in the adapter. When you move an item, the adapter positions of the affected items might change.

fun onRowMoved(
        fromViewHolder: RecyclerView.ViewHolder,
        toViewHolder: RecyclerView.ViewHolder
                Boolean {
    val fromPosition = fromViewHolder.adapterPosition
    val toPosition = toViewHolder.adapterPosition
    val imagesSize = this.images.size

    if (fromPosition != RecyclerView.NO_POSITION && toPosition !=
            RecyclerView.NO_POSITION &&
            fromPosition < imagesSize && toPosition < imagesSize) {
        if (fromPosition < toPosition) {
            for (i in fromPosition until toPosition) {
                Collections.swap(this.images, i, i + 1)
            }
            notifyItemMoved(fromPosition, toPosition)
        } else {
            for (i in fromPosition downTo toPosition + 1) {
                Collections.swap(this.images, i, i - 1)
            }
            notifyItemMoved(fromPosition, toPosition)
        }

        Handler().postDelayed({
                notifyDataSetChanged()
        }, 1000)
        return true
    }
    return false
}