【发布时间】:2020-09-22 14:36:22
【问题描述】:
我正在开发一个应用程序来练习一些计算,它将每个给定的答案和有关练习会话的数据保存到 Room 数据库中以跟踪进度。有一个包含答案的表,还有一个包含会话的表,并且答案表中的每一行都需要包含给出答案的会话的 ID
我正在使用 Room 数据库,因此在写入数据库时使用协程。
当用户点击一个按钮时,答案被评估和保存,会话数据也被更新。 (例如回答的问题数和平均分。)
为此,我需要拥有新创建的会话数据的 ID。所以我正在尝试在 ViewModel 的 init{} 块中调用一个方法,该方法使用延迟并调用 await() 以等待插入会话数据,然后从数据库中获取最后一个条目并更新实例视图模型持有的 SessionData 并且只有当这一切都完成后我才启用按钮,因此在我们知道当前会话 id 之前我们不会尝试保存任何数据。
为了测试这一点,我使用 Log.d() 方法打印出当前会话 ID。 问题是我并不总是得到正确的价值观。有时我得到与前一个会话相同的 id,有时我得到正确的一个(所以 Android Studio 中的 Logcat 看起来像:33、33、35、36、38、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()方法中只保存一次session数据,但是因为我必须调用viewModelJob.cancel()方法来防止内存泄漏,所以在保存之前取消作业是完毕。但我认为如果我可以在这里只保存一次数据,那将是一种更有效的方式。
或者有没有更好的方法来实现我想要做的事情?
提前感谢您的帮助, 最好的问候:阿戈斯顿
【问题讨论】:
标签: android kotlin android-room kotlin-coroutines android-database