java.util.ConcurrentModificationException 正在向房间数据库中插入数据

java.util.ConcurrentModificationException while inserting data into room database

我正在尝试从 firebase firestore 下载数据并将其插入到房间数据库中以供离线使用,并使用 MVVM 架构模式避免时滞,但是当我这样做时我收到 java.util.ConcurrentModificationException 错误我正在将数据插入协程内的房间数据库中。

我的代码

class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {

    private var mDatabase: AppDatabase = AppDatabase.getInstance(application)!!
    private val postListRoom: MutableList<PostRoomEntity> = mutableListOf()
    private val postList: LiveData<MutableList<PostRoomEntity>>? = getPostList2()

    private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
    private val db: FirebaseFirestore = FirebaseFirestore.getInstance()

    private val myTAG: String = "MyTag"

    @JvmName("getPostList")
    fun getPostList(): LiveData<MutableList<PostRoomEntity>>? {
        return postList
    }

    @JvmName("getPostList2")
    fun getPostList2(): LiveData<MutableList<PostRoomEntity>>? {
        var postsDao: PostsDao? = null
        Log.d(myTAG, "postDao getPost is " + postsDao?.getPosts())
        return mDatabase.postsDao()?.getPosts()
//        return postList
    }


    fun loadDataPost() {

        val list2 = mutableListOf<PostRoomEntity>()
        db.collection("Posts")
            .addSnapshotListener { snapshots, e ->
                if (e != null) {
                    Log.w(myTAG, "listen:error", e)
                    return@addSnapshotListener
                }

                for (dc in snapshots!!.documentChanges) {
                    when (dc.type) {

                        DocumentChange.Type.ADDED -> {
                            dc.document.toObject(PostRoomEntity::class.java).let {
                                list2.add(it)
                            }
                            postListRoom.addAll(list2)
                            viewModelScope.launch(Dispatchers.IO) {
                                mDatabase.postsDao()?.insertPost(postListRoom)
                            }

//                            mDatabase.let { saveDataRoom(postListRoom, it) }
                        }
                        DocumentChange.Type.MODIFIED -> {

                        }
                        DocumentChange.Type.REMOVED -> {
                            Log.d(myTAG, "Removed city: ${dc.document.data}")
                        }
                    }
                }
            }

    }

}

PostsDao

@Dao
interface PostsDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertPost(PostEntity: MutableList<PostRoomEntity>)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAllPosts(PostEntity :List<PostRoomEntity>)


    @Query("Select * from PostRoomEntity")
    fun getPosts(): LiveData<MutableList<PostRoomEntity>>

//    @Query("SELECT * FROM notes WHERE id= :id")
//    open fun getNoteById(id: Int): NoteEntity?
}

将 MutableLists 与异步任务一起使用或将它们暴露给外部函数非常容易出错。您正在同时执行这两种操作,这可能会导致它们同时从代码中的两个不同位置进行修改,这可能会导致 ConcurrentModificationException。

您应该使用只读列表来消除这种风险。例如,使用类型为 Listvars 而不是类型为 MutableList 的 vals。

您的代码存在一些其他问题:

  • 您在迭代的每一步都将列表的全部内容添加到主列表中,因此最后一项添加一次,倒数第二项添加两次,依此类推。您还在迭代的每个步骤中将整个分解列表插入到本地数据库中,因此它会随着冗余而呈指数级增长。如果您只是想用更改来更新您的本地数据库,那么您应该一次只插入一行。
  • 在一些地方使用了不必要的可空性。 DAO 或您的 LiveData 没有理由为空。
  • 没有用处的不必要的中间变量。就像您创建一个变量 var postsDao: PostsDao? = null 并记录空值并且从不使用它。
  • 您可以直接公开为 public 的属性的冗余和非惯用 getter。
  • 冗余支持 属性 已保存在 LiveData 中的值。
  • 您可以创建 DAO 函数 suspend,这样您就不必担心使用哪个调度程序来调用它们。
  • DAO 没有理由为 MutableList 而不是 List 插入重载。我认为参数应该只是一个项目。
  • 您可以让单个协程迭代更改列表,而不是启动单独的协程来处理每个单独的更改。

我还建议不要混用匈牙利和非匈牙利会员名称。其实我根本不推荐使用匈牙利语命名,但这是一个偏好问题。

你有两个数据库,但没有任何关于它们的名称来区分它们,这有点令人困惑。

解决这些问题后,您的代码将如下所示,但可能还有其他问题,因为我无法对其进行测试或查看它连接到什么。另外,我不使用 Firebase,但我觉得必须有一种更强大的方法来使您的本地数据库与 Firestore 保持同步,而不是尝试通过侦听器进行单独更改。

class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {

    private val localDatabase: AppDatabase = AppDatabase.getInstance(application)!!
    private val mutablePostList = MutableLiveData<List<PostRoomEntity>>()
    val postList: LiveData<List<PostRoomEntity>> = mutablePostList

    private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
    private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()

    private val myTAG: String = "MyTag"

    fun loadDataPost() {

        db.collection("Posts")
            .addSnapshotListener { snapshot, e ->
                if (e != null) {
                    Log.w(myTAG, "listen:error", e)
                    return@addSnapshotListener
                }

                mutablePostList.value = snapshot!!.documents.map {
                    it.toObject(PostRoomEntity::class.java)
                }

                viewModelScope.launch {
                    for (dc in snapshot!!.documentChanges) {
                        when (dc.type) {
                            DocumentChange.Type.ADDED -> {
                                val newPost = dc.document.toObject(PostRoomEntity::class.java)
                                localDatabase.postsDao().insertPost(newPost)
                            }
                            DocumentChange.Type.MODIFIED -> {

                            }
                            DocumentChange.Type.REMOVED -> {
                                Log.d(myTAG, "Removed city: ${dc.document.data}")
                            }
                        }
                    }
                    saveDataRoom(postListRoom, localDatabase) // don't know what this does
                }
            }

    }

}
@Dao
interface PostsDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertPost(postEntity: PostRoomEntity)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAllPosts(postEntity: List<PostRoomEntity>)

    @Query("Select * from PostRoomEntity")
    fun getPosts(): LiveData<MutableList<PostRoomEntity>>

//    @Query("SELECT * FROM notes WHERE id= :id")
//    open fun getNoteById(id: Int): NoteEntity?
}