【问题标题】:How to return a single (non-LiveData) object from Room using Coroutines如何使用协程从 Room 返回单个(非 LiveData)对象
【发布时间】:2020-04-04 20:55:28
【问题描述】:

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

【问题讨论】:

  • 你不能从协程中同步(立即)返回。他们不是这样工作的。协程是异步的,其数据的消费一直到最终消费者都是异步的。这就是 LiveData 存在的原因 - 使用它,即使您认为它太复杂。
  • @DougStevenson 谢谢你的回答。我不想使用LiveData 的原因是这个值不应该在屏幕上获得实时更新。我想我仍然可以返回LiveData 并只阅读一次value,但这似乎不是一个干净的解决方案。毕竟这叫live数据。
  • LiveData 是单一响应的完美解决方案。事实上,文档甚至用它的“资源”模式证实了这一点。
  • 好的,谢谢。关于您提到的文档部分,您有可以指向我的链接吗?
  • @Query(...) suspend fun getTask(id: Int): Task,你也可以使用FlowRoom代替LiveData

标签: kotlin android-room android-livedata android-architecture-components kotlin-coroutines


【解决方案1】:

如果您不从 Room 返回 LiveData,您将无法从数据库中获取更新。但是,您可以从 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?

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

如果您真的想调用 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
}

【讨论】:

  • 问题是如何从 supsend 函数中返回值
【解决方案2】:

Room 中的挂起函数是主安全的,并且在自定义的 调度员。与您在问题中提到的 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()

}

在道课中

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

【讨论】:

    【解决方案3】:

    Flow 是新的 LiveData!

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

    LiveData 的替代品

    如果您不需要使用 LiveData,您有两种选择:

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

    只检索一个对象/值

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

    道:

    getTask(): 这个方法返回一个Flow&lt;Task&gt;:

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

    视图模型:

    getTask():返回一个Task对象(不是Flow&lt;Task&gt;),也是suspend函数。

    first() 返回第一个元素的终端操作符 flow 然后取消 flow 的收集。抛出 NoSuchElementException 如果流是空的。

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

    片段/活动:

    属性:

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

    不要忘记在不需要片段/活动(即 onClear/onDestory/...)时取消 viewModelJob,以便取消与此相关的所有协程。

    用法

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

        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 }kotlinx.coroutines.flow有很多有用的功能。 Flow is Coroutine 包中发生的最好的事情,哦,对不起,我通过了存储库层,在大多数情况下它只是 viewModel 的重复?。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2019-11-23
        • 1970-01-01
        • 2020-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-23
        • 1970-01-01
        相关资源
        最近更新 更多