Android:await似乎无法使用Room数据库运行

问题描述

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

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

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

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

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

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

但是我不知道为什么。

viewmodel类中:

    
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类的一些详细信息:

@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()方法中仅保存一次会话数据,但是由于我必须调用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时,您不能保证首先使用哪种方法