androidkotlinandroid-recyclerviewitemtouchhelpersectionedrecyclerviewadapter

Get ArrayIndexOutOfBoundsException: length=10; index=-1 when I try to undo a removal of the RecyclerView element


I have a list of the RecyclerView. And I made a swipe removal. Then I made a Snackbar in MainActivity to undo the removal:

val onSwipe = object : OnSwipe(this) {
    override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
        when (direction) {
            ItemTouchHelper.RIGHT -> {
                adapter.removeItem(
                    viewHolder.absoluteAdapterPosition
                )
Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                    .apply {
                        setAction("Undo") {
                            adapter.restoreItem(
                                viewHolder.absoluteAdapterPosition)
                        }
                        show()
                    }
            }
        }
    }

}

Code in adapter:

fun removeItem(pos: Int) {
    listArray.removeAt(pos)
    notifyItemRemoved(pos)
    }

    fun restoreItem(pos: Int) {
        listArray.add(pos, listArray[pos])
        notifyItemInserted(pos)
 }

And when I make the undo operation, my app stops, and I see this in a Logcat:

java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
at com.example.databaselesson.recyclerView.ExpensesAdapter.restoreItem(ExpensesAdapter.kt:79)
at com.example.databaselesson.MainActivity2$onSwipe$1.onSwiped$lambda-1$lambda-0(MainActivity2.kt:391)
at com.example.databaselesson.MainActivity2$onSwipe$1.$r8$lambda$AhJR3pu-3ynwFvPp66LdaLyFdB0(Unknown Source:0)
at com.example.databaselesson.MainActivity2$onSwipe$1$$ExternalSyntheticLambda0.onClick(Unknown Source:4)

Please, help

If you need more code, please, write, and I will send you it


Solution

  • There are two issues here.

    1st: Call viewHolder.absoluteAdapterPosition after notifyItemRemoved shall return -1

    This match the exception in your Logcat since it is telling you that you are trying to get index=-1 from listArray.

    val onSwipe = object : OnSwipe(this) {
        override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
            when (direction) {
                ItemTouchHelper.RIGHT -> {
                    adapter.removeItem(
                        viewHolder.absoluteAdapterPosition //<==Let's say position return 8
                    )
                Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                        .apply {
                            setAction("Undo") {
                                adapter.restoreItem(
                                    viewHolder.absoluteAdapterPosition) //<==Deselected item so it shall return -1
                            }
                            show()
                        }
                }
            }
        }
    
    }
    
    

    2nd: You haven't cached the item object so it will fail to retrieve the correct data

    // Assume that `listArray` = ["A", "B", "C"], `pos` = 1
    fun removeItem(pos: Int) {
        listArray.removeAt(pos) = ["A", "C"]
        notifyItemRemoved(pos)
    }
    
    // `listArray` = ["A", "C"], `pos` = 1 (Assume you get the correct target pos)
    fun restoreItem(pos: Int) { 
        listArray.add(pos, listArray[pos]) //`listArray[1]` = "C", listArray = ["A", "C", "C"]
        notifyItemInserted(pos)
     }
    

    In order to resolve this, you will need to cache both the position and item object in onSwiped call

    val onSwipe = object : OnSwipe(this) {
        override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
            when (direction) {
                ItemTouchHelper.RIGHT -> {
                    val cachedPosition = viewHolder.absoluteAdapterPosition // cache position! 
                    val cachedItem = listArray[cachedPosition] // cache item!
                    adapter.removeItem(cachedPosition)
    
                    Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                        .apply {
                            setAction("Undo") {
                                adapter.restoreItem(cachedPosition, cachedItem) 
                         }
                            show()
                        }
                }
            }
        }
    
    }