当我按下删除按钮时,我的应用程序崩溃 "sometimes"

my app crashes "sometimes" when i press delete button

所以我正在构建用于学习目的的列表应用程序,到目前为止我已经制作了 recyclerview,添加了新项目。我现在正在尝试为 recyclerview 项目实施 multi select selecting 并在 selecting 多个项目后删除它们。项目保存在房间数据库中。我正在使用 selection 跟踪器库来获取 selected recyclerview 项目 ID,然后我开始删除具有这些 ID 的项目。

现在的问题是,当我启动这个应用程序时,这个删除按钮有时会起作用,但在尝试删除更多项目后,它崩溃了。它需要重新启动应用程序才能再次工作,即便如此,它并不总是有效。 我一直在努力寻找解决办法,但到目前为止还没有找到。 如果有人能给我任何指示,我将不胜感激。image of my app. delete button is up in toolbar 我也是 android 开发的新手,如果您需要我的任何其他代码,请随时询问。

堆栈跟踪:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.todolist, PID: 9898
    java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:-1).state:6 androidx.recyclerview.widget.RecyclerView{737e557 VFED..... ......ID 0,154-1080,1396 #7f080207 app:id/toDoRecyclerView}, adapter:com.example.todolist.Adapters.ToDoAdapter@2f75844, layout:androidx.recyclerview.widget.LinearLayoutManager@8cd7d2d, context:com.example.todolist.Activity.MainActivity@50574a3
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6183)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:675)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4085)
        at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3534)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
        at android.view.View.measure(View.java:25466)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2228)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2486)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
        at android.view.Choreographer.doCallbacks(Choreographer.java:796)
        at android.view.Choreographer.doFrame(Choreographer.java:731)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

这是我的道:

interface NoteDao {
    @Query("select * from notes")
    fun getNotes() : LiveData<List<RoomNote>>

    @Query("select * from notes where id = :id")
    suspend fun getNoteById(id: Int) :RoomNote

    @Delete
    suspend fun deleteNote(note: RoomNote)

    @Query("delete from notes where id = :id")
    suspend fun deleteSingleItem(id: Int)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOrUpdateNote(note: RoomNote)
}

数据class:

@Entity(
    tableName = "notes"
)
data class RoomNote(
    @ColumnInfo(name = "creation_date")
    val creationDate: String,
    @ColumnInfo(name = "title")
    val title: String,
    @ColumnInfo(name = "contents")
    val contents: String,
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    val id: Int
)

查看模型:

class ToDoViewModel(private val repository: ToDoRepository) :ViewModel(){

    //get all notes
    val allNotes : LiveData<List<RoomNote>> = repository.allNotes


    //add new item
    suspend fun insert(note: RoomNote)  = viewModelScope.launch{
        repository.insert(note)
    }

    //get single note
    fun getSingleNote(id: Int) = viewModelScope.launch{
        repository.getSingleNote(id)
    }
    // delete note
    suspend fun deleteNote(note: RoomNote) = viewModelScope.launch {
        repository.deleteNote(note)
    }
    suspend fun deleteSingleItem(id: Int) = viewModelScope.launch(Dispatchers.IO) {
        repository.deleteSingleItem(id)
    }

}

class ToDoViewModelFactory(private val repository: ToDoRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(ToDoViewModel::class.java)) {

            return ToDoViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

存储库:

class ToDoRepository(private val noteDao: NoteDao) {

    val allNotes : LiveData<List<RoomNote>> = noteDao.getNotes()

    @WorkerThread
    suspend fun insert(note: RoomNote){
        noteDao.insertOrUpdateNote(note)
    }

    @WorkerThread
    suspend fun getSingleNote(id: Int) {
        noteDao.getNoteById(id)
    }
    suspend fun deleteNote(note: RoomNote){
        noteDao.deleteNote(note)
    }
    suspend fun deleteSingleItem(id: Int){
        noteDao.deleteSingleItem(id)
    }
}

activity:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var adapter: ToDoAdapter
    private var tracker: SelectionTracker<Long>? = null
    var itemsSelected = mutableListOf<Long>()

    private val listViewModel:ToDoViewModel by viewModels{
        ToDoViewModelFactory((application as ToDoApplication).repository)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
        listViewModel.allNotes.observe(this, { newData ->
            adapter.submitList(newData)
        })
        setSupportActionBar(binding.appBar)
        init()
        trackSelectedItems()
    }


    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        binding.appBar.inflateMenu(R.menu.main_activity_toolbar_menu)
        return super.onCreateOptionsMenu(menu)
    }


    override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
        R.id.addNewItem -> {
            val intent = Intent(this, AddNewItem::class.java)
            startActivity(intent)
            true
        }
        R.id.deleteItemsButton ->{
            launchDeletion()
            d("clicked","clicked")
            true
        }
        else -> {
            super.onOptionsItemSelected(item)
        }
    }


    private fun init() {
        adapter = ToDoAdapter()
        binding.toDoRecyclerView.layoutManager = LinearLayoutManager(this)
        binding.toDoRecyclerView.adapter = adapter

    }
    private fun trackSelectedItems() {
        tracker = SelectionTracker.Builder<Long>(
            "selection-1",
            binding.toDoRecyclerView,
            StableIdKeyProvider(binding.toDoRecyclerView),
            ItemLookup(binding.toDoRecyclerView),
            StorageStrategy.createLongStorage()
        ).withSelectionPredicate(SelectionPredicates.createSelectAnything())
            .build()

        adapter.setTracker(tracker)

        tracker?.addObserver(object: SelectionTracker.SelectionObserver<Long>() {
            override fun onSelectionChanged() {
                //handle the selected according to your logic
                itemsSelected.clear()
                itemsSelected.addAll(tracker!!.selection)
                d("itemsSelected","$itemsSelected}")
            }
        })
    }
    private fun launchDeletion() = runBlocking {
        launch {
            itemsSelectedDeletion() }
        }

    private suspend fun itemsSelectedDeletion(){
        for (i in itemsSelected){
            listViewModel.deleteSingleItem(i.toInt())
        }
    }

    inner class ItemLookup(private val rv: RecyclerView)
        : ItemDetailsLookup<Long>() {
        override fun getItemDetails(event: MotionEvent)
                : ItemDetails<Long>? {
            val view = rv.findChildViewUnder(event.x, event.y)
            if(view != null) {
                return (rv.getChildViewHolder(view) as ViewHolder).getItemDetails()
            }
            return null
        }
    }

}

适配器

class ToDoAdapter: ListAdapter<RoomNote, ViewHolder>(WordsComparator()) {

    private var tracker: SelectionTracker<Long>? = null

    fun setTracker(tracker: SelectionTracker<Long>?) {
        this.tracker = tracker
    }
    init {
        setHasStableIds(true)
    }

    override fun getItemId(position: Int): Long = position.toLong()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val current = getItem(position)
        holder.title.text = current.title
        holder.creationDate.text = current.creationDate

        tracker?.let {
            if (it.isSelected(position.toLong())) {
                it.select(position.toLong())
                holder.marked.alpha = 1.0F
            } else {
                it.deselect(position.toLong())
                holder.marked.alpha = 0.0F
            }
        }
    }

}

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

    fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
        object : ItemDetailsLookup.ItemDetails<Long>() {
            override fun getPosition(): Int = adapterPosition
            override fun getSelectionKey(): Long = itemId
        }

    val title: TextView = itemView.findViewById(R.id.title)
    val creationDate: TextView = itemView.findViewById(R.id.creationDate)
    val marked: Button = itemView.findViewById(R.id.checkButton)

    companion object {
        fun create(parent: ViewGroup): ViewHolder {
            val view: View = LayoutInflater.from(parent.context)
                .inflate(R.layout.todo_item, parent, false)
            return ViewHolder(view)
        }
    }


}


class WordsComparator : DiffUtil.ItemCallback<RoomNote>() {
    override fun areContentsTheSame(oldItem: RoomNote, newItem: RoomNote): Boolean {
        return oldItem.id == newItem.id
    }

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

}

工具栏菜单:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/deleteItemsButton"
        android:icon="@mipmap/delete_button"
        android:title="plus"
        app:showAsAction="always"
        />

    <item
        android:id="@+id/addNewItem"
        android:icon="@mipmap/plus_icon"
        android:title="plus"
        app:showAsAction="always"
    />

</menu>

在你的适配器中你有:

setHasStableIds(true)

override fun getItemId(position: Int): Long = position.toLong()

这是矛盾的。当项目被删除时,位置和ids会发生变化,使ids不稳定。

要么删除 setHasStableIds(true),要么想出一种方法来生成实际稳定的项目 ID(例如基于数据中的 id 字段)。