dagoston93
dagoston93

Reputation: 67

Android: await() seems not to work using Room database

I am working on an app to practise some calculations which saves each given answers and data about practise sessions into a Room database for tracking progress. There is a table that contains the answers and there is a table which contains the sessions, and each row in answer table needs to contain the id of the session in which the answer was given

I am using a Room database thus using coroutines when writing to database.

When the user clicks a button the answer is evaluated and saved, and also the session data is updated. (Such as number of questions answered and the average score.)

To achieve this, I need to have the Id of the freshly created session data. So what I am trying is to call a method in the init{} block of the ViewModel which uses a Defferred and call await() to wait for the session data to be inserted and then get the last entry from the database and update the instance of SessionData that the view model holds and only when it is all done I enable the button thus we will not try to save any data before we know the current session id.

To test this out, I am using the Log.d() method to print out the current session id. The problem is that I don't always get the right values. Sometimes I get the same id as previous session was, sometimes I get the correct one (so Logcat in Android Studio looks like: 33,33,35,36,38,38,40,41,42,...etc). However if I get all data from the database and check it out, all the ids are in the database, in correct order, no values are skipped.

For me it seems that await() doesn't actually make the app to wait, it seems to me that the reading of the database sometimes happenes before the writing is complete.

But I have no idea why.

In the ViewModel class:

    
SimpleConversionFragmentViewModel(
    val conversionProperties: ConversionProperties,
    val databaseDao: ConversionTaskDatabaseDao,
    application: Application) : AndroidViewModel(application){ 
  
     private var viewModelJob = Job()  
     private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
     private lateinit var sessionData : SessionData

     ...
 
     init{
         startNewSession()  
     }

     ...

    /**
     *  This method starts and gets the current session
     */
    private fun startNewSession() {
        uiScope.launch {
            /** Initialize session data*/
            sessionData = SessionData() 
            sessionData.taskCategory = conversionProperties.taskCategory
            sessionData.taskType = conversionProperties.taskType

            /**
             *  First insert the new session wait for it to be inserted and get the session inserted
             *  because we need it's ID
             **/
            val createNewSession = async { saveSessionDataSuspend() }
            val getCurrentSessionData = async { getCurrentSessionSuspend() }

            var new = createNewSession.await()
            sessionData = getCurrentSessionData.await() ?: SessionData()
            _isButtonEnabled.value = true //Only let user click when session id is received!!
            Log.d("Coroutine", "${sessionData.sessionId}")
        }
    }
    
    /**
     *  The suspend function to get the current session
     */
    private suspend fun getCurrentSessionSuspend() : SessionData? {
        return withContext(Dispatchers.IO){
            var data = databaseDao.getLastSession()
            data
       }
    }

   /**
     * The suspend function for saving session data
     */
    private suspend fun saveSessionDataSuspend() : Boolean{
        return withContext(Dispatchers.IO) {
            databaseDao.insertSession(sessionData)
            true
        }
    } 

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

And here is some details from the ConversionTaskDatabaseDao class:

@Dao
interface ConversionTaskDatabaseDao {

    @Insert(entity = SessionData::class)
    fun insertSession(session: SessionData)

    @Query("SELECT * FROM session_data_table ORDER BY session_id DESC LIMIT 1")
    fun getLastSession() : SessionData?
    
    ...

}

Has anyone got any idea how to solve this?

My first attempt was actually to save the session data only once in the onCleared() method of the ViewModel, but because I have to call the viewModelJob.cancel() method to prevent memory leaks, the job is cancelled before saving is done. But I think it would be a more efficient way if I could save the data here only once.

Or is there a better way to achive what I am trying to do?

Thanks in advance for any help, Best regards: Agoston

Upvotes: 0

Views: 775

Answers (1)

sergiy tykhonov
sergiy tykhonov

Reputation: 5103

My thought is since you need to wait one suspend method for another and they are already inside coroutine (initiated with launch-builder), you don't need await and you can simplify this:

val createNewSession = async { saveSessionDataSuspend() }
val getCurrentSessionData = async { getCurrentSessionSuspend() }

var new = createNewSession.await()
sessionData = getCurrentSessionData.await() ?: SessionData()

to that:

var new = saveSessionDataSuspend()
sessionData = getCurrentSessionSuspend()

On the contrary when you use await you have no guarantee what method would be first

Upvotes: 1

Related Questions