Cannot modify managed objects outside of a write transaction
SanEffect opened this issue · comments
Your documentation says the following - "Collections are mutable and are backed by their corresponding built-in Kotlin classes. You can add and remove elements in a collection within a write transaction."
But that's not true.
When I run the following code I get "llegalStateException: Cannot modify managed objects outside of a write transaction":
class Board : RealmObject {
@PrimaryKey
var id: String = ""
var name: String = ""
var flashCards: RealmList<FlashCard> = realmListOf()
}
class FlashCard : RealmObject {
var id: String = ""
var frontText: String = ""
var backText: String = ""
}
val board = realm.query<Board>("id == $0", boardId).first().find()
realm.write {
board.flashCards.add(flashCard)
}
Maybe I'm doing something wrong?
Hi @SanEffect. I see that the error message is a bit unclear. The issue is that you are trying to update a frozen object. When querying the realm you get objects tied to the version at the point when the query is executed. The write
-block executes on the most recent version of the realm, and in this version your board
could potentially have been deleted. To update board
you must obtain a reference of the object in the context of the write transaction with findLatest(board)
. If board
has been deleted this will return null
.
realm.write {
findLatest(board)?.flashCards.add(flashCard)
}
Alternatively you can also query for live objects inside the write
-block
realm.write { // this : MutableRealm
val board = this.query<Board>("id == $0", boardId).first().find()
board.flashCards.add(flashCard)
}
Hi @rorbech. Thanks to your explanation I realized what I was doing wrong. Previously I tried this approach:
realm.write { // this : MutableRealm
val board = this.query<Board>("id == $0", boardId).first().find()
board.flashCards.add(flashCard)
}
But my mistake was that I specified not "this.query()", but "realm.query()" (inside a write transaction).
Thank you very much for the detailed explanation! Now adding new elements to the RealmList works perfectly!
@rorbech I think it might be worth keeping this open to track updating the error message to be something that guides the user to a fix.
I'm also getting this same issue in my Compose Multiplatform project, but can't figure out how to fix it. It only happens while I'm updating a RealmList
field, with all other fields it works fine.
override suspend fun addQuizToHistory(
completedQuiz: Quiz,
earnedPoints: Int
): RequestState<Boolean> {
return if (mongoUser != null) {
try {
realm.write {
val currentUser = this.query<QuizUser>(
query = "ownerId == $0", mongoUser!!.id
).first().find()
if (currentUser != null) {
currentUser.history.add(
QuizHistory().apply {
quiz = completedQuiz
points = earnedPoints
timestamp = Clock.System.now().toEpochMilliseconds()
}
)
RequestState.Success(data = true)
} else {
RequestState.Error(message = USER_NOT_FOUND)
}
}
} catch (e: Exception) {
RequestState.Error(message = e.message.toString())
}
} else {
RequestState.Error(MONGO_USER_NULL_MESSAGE)
}
}
Error: Cannot import an outdated object. Use findLatest(object) to find an
@rorbech I've even tried this approach, but still the same issue:
override suspend fun addQuizToHistory(
completedQuiz: Quiz,
earnedPoints: Int
): RequestState<Boolean> {
return if (mongoUser != null) {
try {
realm.write {
val currentUser = this.query<QuizUser>(
query = "ownerId == $0", mongoUser!!.id
).first().find()
findLatest(currentUser!!)?.history?.add(
QuizHistory().apply {
quiz = completedQuiz
points = earnedPoints
timestamp = Clock.System.now().toEpochMilliseconds()
}
)
RequestState.Success(data = true)
}
} catch (e: Exception) {
RequestState.Error(message = e.message.toString())
}
} else {
RequestState.Error(MONGO_USER_NULL_MESSAGE)
}
}
Hi @stevdza-san. I am not really certain what is triggering your issue, but if the completedQuiz
is an object that is already in the realm, then you need to obtain a reference to the most recent version of it with findLatest(completeQuiz)
.
Hi @rorbech. I assume you meant to refer to stevdza-san :)
Hi @rorbech. I assume you meant to refer to stevdza-san :)
Yep, sorry.
@rorbech Hey, thanks for the response. I've managed to fix it somehow. Here's my new function implementation, just in case someone might find it helpful:
override suspend fun addQuizToHistory(
id: ObjectId,
earnedPoints: Int,
successRate: Int,
total: Int,
correct: Int
): RequestState<Unit> {
return if (mongoUser != null) {
try {
realm.write {
val currentUser = this.query<QuizUser>(
query = "ownerId == $0", mongoUser!!.id
).first().find()
if (currentUser != null) {
if (currentUser.history.find { it.quizId == id } == null) {
currentUser.apply {
history.add(
QuizHistory().apply {
quizId = id
points = earnedPoints
success = successRate
totalQuestions = total
correctAnswers = correct
timestamp = Clock.System.now().toEpochMilliseconds()
}
)
}
RequestState.Success(data = Unit)
} else {
RequestState.Error(QUIZ_ALREADY_COMPLETED)
}
} else {
RequestState.Error(USER_NOT_FOUND)
}
}
} catch (e: Exception) {
RequestState.Error(message = e.message.toString())
}
} else {
RequestState.Error(MONGO_USER_NULL)
}
}