首页 > 解决方案 > 如何知道 viewModel 的工作何时完成

问题描述

我试图弄清楚协同程序的工作是如何工作的。基本上,我想启动这个协程FirstFragment,然后导航到SecondFragment并在完成这项工作时得到通知。我打电话getData()FirstFragment onViewCreated()导航到SecondFragment。无论我写getData().isCompleted还是什么getData().invokeOnCompletion { }SecondFragment没有发生。我不知道我是否遗漏了什么或没有正确开始工作或其他什么。

private val _data = MutableStateFlow<GetResource<String>?>(null)
val data: StateFlow<GetResource<String>?> = _data

fun getData() = viewModelScope.launch {
    repository.getData().collect {
        _data.value = it
    }
}

标签: androidkotlinviewmodelkotlin-coroutinesjobs

解决方案


来自数据库的流永远不会完成,因为它应该无限期地监视数据库的更改。它仅在协程被取消时停止。因此,收集此类 Flow 的 Job 将永远不会完成。此外,如果您getData()再次调用 repo,您​​每次都会获得一个新的 Flow 实例。

无论您在做什么,您都需要确保在两个片段之间使用相同的 ViewModel 实例,方法是将其范围限定为 Activity。(by activityViewModels()例如使用。)这是为了viewModelScope在片段之间的过渡期间不会被取消。

如果您需要的只是一次来自 repo 的单个项目,那么最简单的方法可能是从 repo 中公开一个挂起函数而不是 Flow。然后把它变成一个延迟。也许通过将其设为 a Lazy,您可以有选择地决定何时开始检索该值。lazy如果您只想在第一个 Fragment 开始时立即开始检索值,请省略。

// In the shared view model:
val data: Deferred<GetResource<String>> by lazy { 
    viewModelScope.async {
      repository.getData() // suspend function returning GetResource<String>
    }
  }

fun startDataRetrieval() { data } // access the lazy property to start its coroutine

// In second fragment:
lifecycleScope.launch {
  val value = mySharedViewModel.data.await()
  // do something with value
}

但是,如果您必须拥有 Flow,因为您将其用于其他目的:

如果您只想要 Flow 中的第一个可用值,请让第二个 Fragment 监视您的dataStateFlow 的第一个有效值。

lifecycleScope.launch {
  val value = mySharedViewModel.data.filterNotNull().first()
  // do something with first arrived value
}

而且您可以使用 SharedFlow,这样您就不必使数据类型可以为空。如果你这样做,你可以省略filterNotNull()上面。shareIn在您的 ViewModel 中,执行此操作比必须使用支持属性并手动收集源代码的代码更容易。

val data: SharedFlow<GetResource<String>> = repository.getData()
  .shareIn(viewModelScope, replay = 1, SharingStarted.Eagerly)

如果您需要在开始收集到 SharedFlow 之前等待,那么您可以使属性变得惰性。


推荐阅读