【问题标题】:java.util.ConcurrentModificationException while inserting data into room database将数据插入房间数据库时出现 java.util.ConcurrentModificationException
【发布时间】:2021-07-09 17:34:17
【问题描述】:

我正在尝试从 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?
}

【问题讨论】:

    标签: android kotlin mvvm android-room


    【解决方案1】:

    将 MutableLists 与异步任务一起使用或将它们暴露给外部函数非常容易出错。你同时做这两件事,这可能导致它们同时在代码中的两个不同位置被修改,这可能导致 ConcurrentModificationException。

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

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

    • 您在迭代的每一步都将列表的全部内容添加到主列表中,因此最后一项添加一次,倒数第二项添加两次,依此类推。您还在迭代的每个步骤中将整个分解列表插入到本地数据库中,因此它甚至会以指数方式与冗余相乘。如果您只是想通过更改来更新本地数据库,那么您一次只能插入一行。
    • 在一些地方使用了不必要的可空性。 DAO 或您的 LiveData 没有理由永远为空。
    • 无用的不必要的中间变量。就像您创建一个变量 var postsDao: PostsDao? = null 并记录空值并且从不使用它。
    • 可以直接公开为公共的属性的冗余和非惯用 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?
    }
    

    【讨论】:

      猜你喜欢
      • 2023-01-12
      • 1970-01-01
      • 2020-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-13
      • 1970-01-01
      • 2020-09-10
      相关资源
      最近更新 更多