【问题标题】:How to know when job from viewModel is done如何知道 viewModel 的工作何时完成
【发布时间】:2021-08-26 11:04:44
【问题描述】:

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

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

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

【问题讨论】:

    标签: android kotlin viewmodel kotlin-coroutines jobs


    【解决方案1】:

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

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

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

    // 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 监控您的 data StateFlow 的第一个有效值。

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

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

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

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

    【讨论】:

    • 你说(Use by activityViewModels() for example.) This is so the viewModelScope won't be cancelled during the transition between ActivitiesActivity之间的过渡我觉得应该是Fragments
    【解决方案2】:

    同意@Tenfour04 的回答,我想多贡献一点。 如果您真的想控制作业或Structured Concurrency,我建议您使用自定义方式处理协程,而不是将您的代码与viewModelScope 耦合。

    您需要确保以下几点:
    1- 发生取消或异常时会发生什么
    2- 你必须管理协程的生命周期(CoroutineScope)
    3- 取消范围,取决于您现在面临的问题等用例
    4- ViewModel 的范围,例如:它与活动(共享 ViewModel)或特定片段相关联。

    如果您没有仔细处理前 3 个中的任何一个,您更有可能泄漏协程,您的应用程序肯定会出现不当行为。
    每当您以自定义方式启动任何协程时,您都必须确保, 什么是生命周期,什么时候结束,这很重要,它可能会导致真正的问题
    幸运的是,我有这个使用 Jobs 的 CustomViewModel 示例:Structured Concurrency sample code

    【讨论】:

    • 您的示例公开将 ViewModel 本身公开为 CoroutineScope,因此将 cancelScope() 设为 internal 没有意义。我认为它也应该在销毁时自行取消,因为外部 Fragments 无法轻易知道 ViewModel 何时超出范围并从外部取消它们。我认为将任何自定义范围作为私有成员而不是让外部类负责管理范围生命周期是最干净的。
    • @Tenfour04 你是对的,但你对这个存储库是错误的,我没有只为了取消范围而制作那个样本。我在不使用实际 ViewModel() 的情况下构建自定义视图模型。关于cancelScope(),它正在Activity的onDestroy()中被取消。
    • 我的意思是,internal fun cancelScope() 对公众 fun cancel() 来说是多余的,您通过让 ViewModel 实现 CoroutineScope 来公开它。
    猜你喜欢
    • 1970-01-01
    • 2010-10-11
    • 2012-01-13
    • 1970-01-01
    • 2016-04-21
    • 2019-07-05
    • 2018-01-16
    • 1970-01-01
    • 2014-03-27
    相关资源
    最近更新 更多