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。
您应该使用只读列表来消除这种风险。例如,使用类型为 List
的 var
s 而不是类型为 MutableList 的 val
s。
您的代码存在一些其他问题:
- 您在迭代的每一步都将列表的全部内容添加到主列表中,因此最后一项添加一次,倒数第二项添加两次,依此类推。您还在迭代的每个步骤中将整个分解列表插入到本地数据库中,因此它会随着冗余而呈指数级增长。如果您只是想用更改来更新您的本地数据库,那么您应该一次只插入一行。
- 在一些地方使用了不必要的可空性。 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?
}
我正在尝试从 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。
您应该使用只读列表来消除这种风险。例如,使用类型为 List
的 var
s 而不是类型为 MutableList 的 val
s。
您的代码存在一些其他问题:
- 您在迭代的每一步都将列表的全部内容添加到主列表中,因此最后一项添加一次,倒数第二项添加两次,依此类推。您还在迭代的每个步骤中将整个分解列表插入到本地数据库中,因此它会随着冗余而呈指数级增长。如果您只是想用更改来更新您的本地数据库,那么您应该一次只插入一行。
- 在一些地方使用了不必要的可空性。 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?
}