如何使用Coroutines从Room返回单个(非LiveData)对象



Room在后台线程上自动执行返回LiveData的查询。但我想返回一个未封装到LiveData中的值(因为我不想要实时更新)。如何使用协程实现这一点?

如何从该函数返回Task对象?

fun getTask(id: Int): Task {
viewModelScope.launch {
repository.getTask(id)
}
}

此函数位于ViewModel中。它将调用转发到DAO:

@Query("SELECT * FROM task_table WHERE id = :id")
fun getTask(id: Int): Task

如果不从Room返回LiveData,则不会从DB获得更新。但是,您可以从viewModel返回LiveData。

val data = liveData {
emit(repository.getTask(id))
}

liveData扩展函数在协同程序中运行,然后您可以使用DAO的挂起版本来正确处理后台。

@Query("SELECT * FROM task_table WHERE id = :id")
suspend fun getTask(id: Int): Task?

如果您在查询中没有使用聚合函数,那么您需要做的一件大事就是确保它可以为null。

如果你真的想调用viewModel中的方法来返回任务,你应该从你的活动/片段(不推荐)运行启动

ViewModel
suspend fun getTask(id: Int): Task {
repository.getTask(id)
}
Activity/Fragment
lifecycleScope.launch {
val task = viewModel.getTask(id)
// Do What you want with the task
}

Flow是新的LiveData

我之前在两个项目中遇到过类似的问题,每个项目的解决方法都不同。但最近我学会了使用Flow,这似乎是迄今为止最干净的方法。

LiveData的替代方案

如果你不需要使用LiveData,你有两个选择:

  1. 通过@query检索Cursor,适用于重构旧项目
  2. 使用Flow,适用于新项目

仅检索一个对象/值

  1. LiveDate:您可以取消订阅LiveData,在第一次获取后删除观察者。在我看来不干净
  2. 流:如果需要,可以只检索单个对象/值,然后停止流收集

刀:

getTask():此方法返回一个Flow<Task>:

@Query("SELECT * FROM task_table WHERE id = :id")
fun getTask(id: Int): Flow<Task>

视图模型:

getTask():返回一个Task对象(不是Flow<Task>),也是suspend函数。

first()返回由流,然后取消流的收集。抛出NoSuchElementException如果流是空的。

suspend fun getTask(id: Int): Task {
return dao.getTask(id).first()
}

片段/活动:

属性:

private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

当不需要片段/活动时,不要忘记取消viewModelJob,也就是onClear/onDestory/。。。所以所有与此密切相关的协同活动都被取消了

用法

现在,每当我们想从那个挂起的函数中检索对象Task时,我们都需要在一个挂起或协程中。因此,使用launch构建器来创建协程在这里是合适的(因为我们不希望从该构建器中返回任何返回对象,所以我们只想运行一个挂起函数,否则async将返回一个延迟函数)。

onCreate() / onCreateView()
.
..
...
uiScope.launch() {
// Here are the Task & Main-UI
val task = viewModel.getTask(1)
binding.taskTitleTextView.text = task.title
}

如果我们不使用first(),那么我们需要收集流viewModel.getTasks().collect{ it }在kotlin.coroutines.flow中有很多有用的函数。Flow is是Coroutine包中发生的最好的事情,哦,很抱歉我通过了存储库层,在大多数情况下,它只是viewModel的重复。

Room中的Suspend函数是主要的安全函数,在自定义的调度员。与您在问题中提到的LiveData相同。以下是实现相同的示例

viewmModel类中的某些函数内部

viewModelScope.launch {
// suspend and resume make this database request main-safe
// so our ViewModel doesn't need to worry about threading
someLiveData.value =
repository.getSomething()
}

存储库类

suspend fun getSomething(): List<Something> {
return dao.getSomething()

}

In-Dao类

@Query("select * from tableName")
suspend fun getSomething(): List<Something>

解决方法之一是立即返回Deferred对象,然后在返回Deferred 时调用.await()

fun getTaskAsync(id: Int): Deferred<Task> = viewModelScope.async {
repository.getTask(id)
}
//call-site
getTaskAsync(id).await() // <- this is suspension point

最新更新