Search code examples
kotlinrealm

How to clear this error Realm Kotlin android! [RLM_ERR_WRONG_TRANSACTION_STATE]: Trying to modify database while in read transaction


type here

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.delete
import io.realm.kotlin.ext.query
import io.realm.kotlin.ext.toRealmSet
import io.realm.kotlin.query.RealmResults
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {
    private val realm = RealmApplication.realm
    private val _usersLiveData = MutableLiveData<List<User>>()
    val usersLiveData: LiveData<List<User>> = _usersLiveData
    private val _currentUser = MutableLiveData<User>()
    val currentUser : LiveData<User> = _currentUser
    fun addUser(
        email : String ,
        username : String ,
        password : String ,
        otp : String
    ) {
        viewModelScope.launch {
            realm.write {
                val user = User().apply {
                    this.username = username
                    this.email = email
                    this.password = password
                }
                copyToRealm(user , updatePolicy = UpdatePolicy.ALL)
                val generateOtp = Otp().apply {
                    this.code = otp
                    this.id = user.email.toString()
                }
                copyToRealm(generateOtp , updatePolicy = UpdatePolicy.ALL)
            }
        }
    }

    fun verifyOtp(
        enteredOtp : String ,
        email : String ,
        callback : (success : Boolean) -> Unit
    ) {
        viewModelScope.launch {
            try {
                val otpObject = realm.query<Otp>("id == $0" , email).find().first()
                if (otpObject.code == enteredOtp) {
                    realm.write {
                        this.delete<Otp>() // Delete the used OTP
                    }
                    callback(true)
                    realm.query<User>("email == $0", email).find().first().also {
                        realm.writeBlocking {
                            findLatest(it)?.isLoggedIn = true
                        }
                    }

                } else {
                    callback(false)
                }
            } catch (exception : Exception) {
                Log.e("OtpViewModel" , "Error verifying user: ${exception.message}")
            }

        }
    }

    fun loginUser(email : String, password : String, otp : String, callback : (success : Boolean) -> Unit){
        viewModelScope.launch {
            try {
                val userObject = realm.query<User>("email == $0 AND password == $1", email, password).find().first()
                if(userObject.isLoggedIn){
                    val generateOtp = Otp().apply {
                        this.code = otp
                        this.id = email
                    }
                    realm.write {
                        copyToRealm(generateOtp , updatePolicy = UpdatePolicy.ALL)
                    }
                    realm.query<User>("email == $0", email).find().first().also {
                        realm.writeBlocking {
                            findLatest(it)?.isLoggedIn = true
                            findLatest(it)?.currentUser = true
                        }
                    }
                    callback(true)
                }else{
                    callback(false)
                }
            }catch (exception : Exception){
                callback(false)
                Log.e("LoginViewModel" , "User Login Failed: ${exception.message}")
            }
        }
    }
    fun checkUserAlreadyExists(email: String, username: String, callback : (success : Boolean) -> Unit){
        viewModelScope.launch {
            try {
                realm.query<User>("email == $0 OR username == $1", email, username).find().first()
                callback(true)
            }catch (exception : Exception){
                callback(false)
                Log.e("SignupViewModel" , "User not exists: ${exception.message}")
            }
        }
    }

    fun setCurrentUser(selectedUser: User) {
        viewModelScope.launch {
            try {
                realm.query<User>("email == $0", selectedUser.email).find().first().also {
                    realm.writeBlocking {
                        findLatest(it)?.currentUser = true
                    }
                    Log.e("SignupViewModel" , "User updated: Done")
                }
                realm.query<User>("email != $0",selectedUser.email).find().also {
                    realm.writeBlocking{
                        this.apply {
                            it.forEach {
                                it.currentUser = false
                            }
                        }
                    }
                    Log.e("SignupViewModel2" , "User updated: Done")
                }
            }catch (exception : Exception){
                Log.e("SignupViewModel" , "User not exists: ${exception.message}")
            }
            
        }
    }
    fun getAllUsers() {
        viewModelScope.launch {
            val userResults: RealmResults<User> = realm.query<User>().find()
            val users = userResults.toList()
            _usersLiveData.postValue(users)
        }
    }
    fun currentUser(){
        viewModelScope.launch {
            try {
                val currentUserResult = realm.query<User>("currentUser==$0", true).find().first()
                _currentUser.postValue(currentUserResult)
                val list = currentUserResult.username
                Log.e("HomeViewModel" , "Current User is: $list")
            }catch (exception : Exception){
                Log.e("HomeViewModel" , "User not found: ${exception.message}")
            }

        }

    }

}`

Error occurs in setCurrentUser Method...other users is not getting updated

The selected user must become true and other users parameters must be false.But keep on getting this error.I tried all the mentioned details in documentations but nothing seems to work. Please if anyone can solve this it would be of great help

[RLM_ERR_WRONG_TRANSACTION_STATE]: Trying to modify database while in read transaction


Solution

  • Explanation of the Solution The original issue was a RLM_ERR_WRONG_TRANSACTION_STATE error, which indicates that a database modification was attempted during a read transaction. To resolve this, the setCurrentUser method was updated to ensure all modifications occur within proper write transactions. Here’s a step-by-step explanation of how this was achieved:

    Step-by-Step Solution

    Query the Selected User:

    We start by finding the user who should be set as the current user based on their email. This is done within a write transaction to allow database modifications.

    realm.query<User>("email == $0", selectedUser).find().first().also {
        realm.writeBlocking {findLatest(it)?.currentUser = true}
        Log.d("SignupViewModel", "User updated: Done")
    }
    

    Explanation:

    realm.query<User>("email == $0", selectedUser).find().first() retrieves the user with the specified email. realm.writeBlocking is used to initiate a write transaction. Within this block, findLatest(it)?.currentUser = true sets the currentUser property to true.

    Query and Update Other Users:

    Next, we retrieve all other users whose email does not match the selected user’s email. These users need to have their currentUser property set to false, which is done within write transactions.

    val otherUsers = realm.query<User>("email != $0", selectedUser)
    for (user in otherUsers.find()) {
        realm.write {
            findLatest(user)?.currentUser = false
        }
    }
    Log.d("SignupViewModel", "Other User update: Done")
    

    Explanation:

    realm.query<User>("email != $0", selectedUser) retrieves users whose email differs from the selected user's email. We iterate over the result set using a for loop. For each user, a write transaction (realm.write) is started to safely update the currentUser property to false.

    Error Handling:

    Proper error handling ensures that any issues during the process are logged, facilitating debugging.

    catch (exception: Exception) {
        Log.e("SignupViewModel", "User not exists: ${exception.message}")
    }
    

    Explanation:

    The catch block captures any exceptions that occur during the execution and logs an error message with Log.e, which helps in diagnosing issues.

    Final Code

    Here is the complete setCurrentUser function with the explanations embedded within the code:

    fun setCurrentUser(selectedUser: String) {
        viewModelScope.launch {
            try {
                // Query and update the selected user
                realm.query<User>("email == $0", selectedUser).find().first().also {
                    realm.writeBlocking {
                        findLatest(it)?.currentUser = true
                    }
                    Log.d("SignupViewModel", "User updated: Done")
                }
                
                // Query and update other users
                val otherUsers = realm.query<User>("email != $0", selectedUser)
                for (user in otherUsers.find()) {
                    realm.write {
                        findLatest(user)?.currentUser = false
                    }
                }
                Log.d("SignupViewModel", "Other User update: Done")
            } catch (exception: Exception) {
                Log.e("SignupViewModel", "User not exists: ${exception.message}")
            }
        }
    }
    

    Summary

    1. Initiating Write Transactions: All database modifications are performed within write transactions to ensure data integrity and avoid transaction state errors.

    2. Updating Users: The method first updates the selected user’s currentUser status and then updates all other users to ensure only one user is marked as the current user.

    3. Logging and Error Handling: Logging is used to track the progress and catch any exceptions to help with debugging.

    By following these steps, the function ensures the correct user is set as the current user and all other users are updated accordingly, all while maintaining proper transaction management.