Android: await() 似乎无法使用 Room 数据库

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

我正在开发一个应用程序来练习一些计算,该应用程序将每个给定的答案和有关练习会话的数据保存到 Room 数据库中以跟踪进度。有一个 table 包含答案,还有一个 table 包含会话,答案中的每一行 table 需要包含给出答案的会话的 ID

我正在使用 Room 数据库,因此在写入数据库时​​使用协程。

当用户单击按钮时,系统会评估并保存答案,同时还会更新会话数据。 (例如回答的问题数和平均分。)

为此,我需要有新创建的会话数据的 Id。所以我正在尝试的是在 ViewModel 的 init{} 块中调用一个方法,该方法使用 Defferred 并调用 await() 等待插入会话数据,然后从数据库中获取最后一个条目并更新视图模型持有的 SessionData 实例,只有当它全部完成时,我才启用按钮,因此在我们知道当前会话 ID 之前我们不会尝试保存任何数据。

为了对此进行测试,我使用 Log.d() 方法打印出当前会话 ID。 问题是我并不总能得到正确的价值观。有时我得到的 ID 与上一会话相同,有时我得到正确的 ID(因此 Android Studio 中的 Logcat 看起来像:33,33,35,36,38,38,40,41, 42,...等)。但是,如果我从数据库中获取所有数据并检查出来,所有 ID 都在数据库中,顺序正确,没有跳过任何值。

对我来说,似乎 await() 实际上并没有让应用程序等待,在我看来,数据库的读取有时发生在写入完成之前。

但我不知道为什么。

在 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()
    }
}

这里是 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?
    
    ...

}

有人知道如何解决这个问题吗?

我的第一次尝试其实是在ViewModel的onCleared()方法中只保存了一次session数据,但是因为要调用viewModelJob.cancel()方法来防止内存泄露,所以作业在保存完成前被取消。但我认为如果我能在这里只保存一次数据,那将是一种更有效的方式。

或者是否有更好的方法来实现我想要做的事情?

在此先感谢您的帮助, 最诚挚的问候:阿戈斯顿

我的想法是因为你需要等待一个挂起方法等待另一个挂起方法并且它们已经在协同程序中(用 launch-builder 启动),你不需要 await 并且你可以简化它:

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

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

到那个:

var new = saveSessionDataSuspend()
sessionData = getCurrentSessionSuspend()

相反,当您使用 await 时,您无法保证先使用哪种方法