【问题标题】:Should I use ViewModelScope independent of Fragment's lifecycle?我应该独立于 Fragment 的生命周期使用 ViewModelScope 吗?
【发布时间】:2021-05-11 06:46:18
【问题描述】:

TL;DR

对于一次又一次添加/删除的片段实例,其 ViewModel 中的viewModelScope 仅在第一次弹出片段之前有效。此后,viewModelScope 变为非活动状态。当我重新添加片段时,如何重新初始化viewModelScope?还是我应该寻找更好的实现来保持它的活跃?

场景:

我在一个旧项目中使用RxJavaViewModel 从网络中检索数据,并将更新发送到 UI,我使用了 LiveData。

以下是我在片段中初始化典型 ViewModel 的方式:

private val fruitsViewModel by viewModels<FruitsViewModel>()

下面是上面提到的ViewModel:

class FruitsViewModel: ViewModel {

    private var category: String = FruitCategories.DEFAULT

    private val _fruits = MutableLiveData<List<Fruit>>()
    fun fruits(): LiveData<List<Fruit>> = _fruits

    fun updateCategory(newCategory: String) {
        this.category = newCategory
        fetchFruits()
    }

    fun fetchFruits() { ... }
}

在 Fragment 的onViewCreated() 中,我调用了fruitsViewModel.fetchFruits()。这很好用(没有任何明显的问题)。

今天,我使用协程切换了 RxJava 特定代码,以在我的 ViewModel 中检索网络数据。像这样的:

fun fetchFruits() {

    /* this.fruitsJob = */ 
    viewModelScope.launch {
        ...
    }
}

这也很好用,即使我替换了一个片段并返回它。但是,如果我保留在 Activity 中声明的片段实例,并再次使用它,viewModelScope.launch {} 将不再起作用。像这样的:

class FruitsActivity: AppCompatActivity() {

    private val fruitsFragment = FruitsFragment()

    private fun showFruitsFragment() {
        supportFragmentManager
            .beginTransaction()
            .replace(getFragmentContainerId(), fruitsFragment)
            .addToBackStack(fruitsFragment.tag)
            .commit()
    }

    private fun removeFragments() {
        val manager = supportFragmentManager
        if (manager.backStackEntryCount > 0) {

            val first: FragmentManager.BackStackEntry = manager.getBackStackEntryAt(0)
            manager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
        }
    }
} 

保留片段实例的原因是只加载一次 API 数据,之后,它应该只是在单击按钮时出现,没有任何闪烁/加载。

在我调用removeFragments() 之后,fruitsViewModel 已经调用了它的onCleared()保留 fruitsFragment 的声明。因此,它的viewModelScope 变为非活动状态,如果我通过调用showFruitsFragment() 再次添加fruitsFragment,这将不再检索网络数据。这不是我的 Rxjava 实现的情况,因为我没有使用 viewModelScope

我想我可以通过将fruitsViewModel 链接到FruitsActivity 的实例来解决这个问题。但是,我想知道这是否可以被认为是最好的方法。

【问题讨论】:

  • The reason for keeping the fragment instance is to load API data only once 所以听起来你需要一个共享视图模型,不是吗?为什么不将视图模型附加到 Activity 并让它根据需要进行服务调用
  • 同意@a_local_nobody 。由于无论附加和分离多少次,您都可以使片段保持更长的时间(类似于 Activity 的生命周期),因此最好使用 Activity 的范围或 Activity 的视图模型。
  • 顺便说一句,由于您希望独立于片段的生命周期保留加载的数据,因此数据应该存储在活动而不是片段中。 IMO,保留片段实例不是一个好习惯。

标签: android android-fragments kotlin-coroutines android-viewmodel


【解决方案1】:

是的,您应该以不同的方式确定 viewModel 的范围,在您的情况下,将 fruitsViewModel 范围限定为 FruitsActivity 是完全有意义的。

我不确定您的 RxJava 代码是如何工作的,没有看到我猜测的代码,这与 disposed 不正确有关。

此外,即使您要创建一个比 viewModelScope 寿命更长的范围(例如:在活动中创建和维护 CoroutineScope),我的主要问题是该方法不直观/不自然,人们将很难检查CoroutineScope 的维护方式和位置

【讨论】:

  • 同意,谢谢。 p.s. RxJava 相关的实现在 ViewModel 的onCleared() 方法中处理了所有的disposables。这允许在 onCleared() 之后进行新的调用,因为 RxJava 没有链接到任何生命周期。但是,对于协程,我依赖于链接到生命周期(?)的viewModelScope
  • 嗯,我想知道实现有什么不同,因为技术上viewModelScope取消了onCleared()的协程
  • RxJava 实现不依赖于协程或viewModelScopefetchFruits() 就像任何其他类中的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 2019-07-21
  • 1970-01-01
相关资源
最近更新 更多