Получить ArrayIndexOutOfBoundsException: длина = 10; index=-1, когда я пытаюсь отменить удаление элемента RecyclerView

У меня есть список RecyclerView. И сделал удаление свайпом. Затем я сделал Snackbar в MainActivity, чтобы отменить удаление:

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()
                    }
            }
        }
    }

}

Код в адаптере:

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

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

И когда я делаю операцию отмены, мое приложение останавливается, и я вижу это в 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)

Пожалуйста помоги

Если вам нужно больше кода, пожалуйста, напишите, и я пришлю вам его

1
0
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Здесь есть две проблемы.

1-й: Позвоните viewHolder.absoluteAdapterPosition после того, как notifyItemRemoved вернется -1

Это соответствует исключению в вашем Logcat, поскольку оно говорит вам, что вы пытаетесь получить index=-1 из 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()
                    }
            }
        }
    }

}

2-й: вы не кэшировали объект элемента, поэтому он не сможет получить правильные данные

// 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)
 }

Чтобы решить эту проблему, вам нужно будет кэшировать как позицию, так и объект элемента в вызове onSwiped.

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()
                    }
            }
        }
    }

}



Предположим, что код псевдо, так как я не эксперт по Котлину. Может быть какая-то синтаксическая ошибка. Но концепция заключается в том, что вы должны кэшировать вещь, прежде чем удалить ее, если вы хотите восстановить рано или поздно.

Android Newbie A 16.11.2022 02:59

Когда вы удаляете элемент и делаете notifyItemRemoved, ViewHolder, используемый для отображения этого элемента, удаляется из списка. Поскольку он ничего не отображает, его absoluteAdapterPosition имеет значение NO_POSITION или -1:

Возврат int

Положение адаптера элемента с точки зрения RecyclerView, если он все еще существует в адаптере и привязан к допустимому элементу. NO_POSITION если элемент был удален из адаптера, notifyDataSetChanged был вызван после последнего прохода макета или ViewHolder уже был перезапущен.

Поэтому, когда вы нажмете кнопку ОТМЕНИТЬ, этот viewholder вернет -1, что не является допустимым индексом для вашего списка данных!

Вероятно, вам следует сохранить фактическую позицию, которую вы удаляете:

override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
    // get the position first, and store that value
    val position = viewHolder.absoluteAdapterPosition

    when (direction) {
        ItemTouchHelper.RIGHT -> {
            // using the position we stored
            adapter.removeItem(position)
            // you don't have to use apply here if you don't want - it's designed
            // to be chained (fluent interface where each call returns the Snackbar)
            Snackbar.make(binding.rv, "Deleted", Snackbar.LENGTH_SHORT)
                // using that fixed position value again
                .setAction("Undo") { adapter.restoreItem(position) }
                .show()
        }
    }
}

Таким образом, вы удаляете определенную позицию элемента, и если нажата кнопка отмены, вы используете то же значение позиции, чтобы восстановить ее. Вы не полагаетесь на состояние используемого ViewHolder.


Также это:

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

ничего не восстанавливает? Он просто вставляет копию элемента pos в ту же позицию. Поскольку ваш removeItem фактически удаляет элемент из списка, его невозможно вернуть, если вы его где-нибудь не сохраните. У вас может быть переменная lastDeletedItem, которую вы обновляете в removeItem, которая восстанавливает restoreItem:

var lastDeletedItem: Item? = null

fun removeItem(pos: Int) {
    // store the deleted item
    lastDeletedItem = listArray[pos]
    listArray.removeAt(pos)
    notifyItemRemoved(pos)
}

fun restoreItem(pos: Int) {
    // restore the last thing that was deleted at this position
    lastDeletedItem?.let {
        listArray.add(pos, it)
        notifyItemInserted(pos)
    }
}

Но тогда у вас есть элемент, который был удален в одном месте, а позиция в другом (лямбда закусочной), поэтому вы можете просто соединить их вместе - сохраните lastDeletedPosition в removeItem и укажите это в restoreItem (не пропускайте pos in), или заставьте restoreItem взять pos и item и получить элемент в обратном вызове смахивания, когда вы сохраняете текущую позицию адаптера

Другие вопросы по теме