具有共享视图模型的Android导航组件

问题描述 投票:0回答:1

视图模型与活动或附加的片段一起生存和死亡。这有一定的后果,超出我的原因是没有人询问(如果我们将导航架构纳入图片中)。

根据最新的Android博客和导航框架的工作方式,我们建议进入Single Activity Multiple Fragments verse。

据说我有以下应用程序设计。

Activity A (Application Entry Point)
----------
Fragment (A) (Uses ViewModel AB)
Fragment (B) (Uses ViewModel AB)
Fragment (C) (Uses ViewModel CDE)
Fragment (D) (Uses ViewModel CDE)
Fragment (E) (Uses ViewModel CDE)

现在,因为我使用共享的视图模型,这意味着我的视图模型将附加到活动。然而,这似乎是漏洞。就像我从A到E一直遍历并现在回来弹出片段到片段B一样,视图模型CDE应该被销毁,但它不会因为它连接到活动。

此外,我们无法将视图模型连接到片段,因为我们将共享其数据。

事实上,只有我提出这个问题,才能让我相信我的理解是错误的。如果能够对这种情况给予适当的了解,我会很高兴。

android android-architecture-components android-jetpack
1个回答
2
投票

这真的是一个问题,一直是reported to Google

幸运的是,自Navigation 2.1.0-alpha02这个问题已经解决了。你可以找到更改日志heredocument

您现在可以通过kotlin用户的by navGraphViewModels()属性委托或使用添加到getViewModelStore()NavController API创建在导航图级别确定范围的ViewModel。

首先,您应该在导航图设计器中选择一些片段,然后右键单击它们并选择Move to Nested Graph以创建一个新图形,它将用作“范围”,如下所示:

class DetailFr : Fragment() {
    private val vm: DetailViewModel by navGraphViewModels(R.id.main_nav_graph)
}

您可以了解更多关于Nested Graph here的信息。


1
投票

我以为这是你的问题:

就像我从A到E一直遍历并现在回来弹出片段到片段B一样,视图模型CDE应该被销毁,但它不会因为它连接到活动。

您希望使用ViewModel在多个片段之间共享数据,但是您希望确保在片段导航到某个屏幕时ViewModel的数据将被销毁。

我的建议解决方案是:

  1. 在ViewModel类中创建一个Destroy Data Function,它将通过将其值覆盖为空值来销毁ViewModel的数据,例如“” class CDEViewModel : ViewModel() { var dataString: String = "" fun destroyViewModelData() { // Function that will Destroythe Data dataString= "" } }
  2. 现在,您可以在需要确保ViewModel数据正在清除/销毁时调用Fragment中的destroyViewModelData函数 class FragmentE { private lateinit var cdeViewModel : CDEViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Initialize your ViewModel cdeViewModel = ViewModelProviders.of(this).get(CDEViewModel ::class.java) } override fun onStart() { super.onStart() // Set your Value Here cdeViewModel.dataString = "String 1" } override fun onStop() { super.onStop() // Reset/Destroy Data when Screen is Being Close/Navigate to other Screen // After Call this function, in Whatever Screen, the ViewModel previous Set ""String 1"" Data is Clear/Destroy and become "" empty value. cdeViewModel.destroyViewModelData() } }

在你的情况下,你可以在FragmentE的onStop()上调用destroyViewModelData函数,所以当你从FragmentE导航到FragmentB时,CDEViewModel的数据都变成“”空字符串,这意味着它已经被重置/销毁。

希望这个简单的解决方案有所帮谢谢。


0
投票

每个LifecycleOwner(即片段或活动)都将其模型保存在具有clear()函数的ViewModelStore中。但是,清除扫描ViewModelStore中的所有模型,在您的情况下是不合需要的(ViewModel AB和ViewModel CDE都将从Activity的ViewModelStore中清除)。此问题的一种可能解决方案是拥有可在必要时安全清除的每个ViewModel存储:

class MainActivity : AppCompatActivity() {

val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()

inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
    val factory = object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            //Put your existing ViewModel instantiation code here,
            //e.g., dependency injection or a factory you're using
            //For the simplicity of example let's assume
            //that your ViewModel doesn't take any arguments
            return modelClass.newInstance()
        }
    }

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
    val viewModelKey = VIEWMODEL::class
    var viewModelStore = individualModelStores[viewModelKey]
    return if (viewModelStore != null) {
        viewModelStore
    } else {
        viewModelStore = ViewModelStore()
        individualModelStores[viewModelKey] = viewModelStore
        return viewModelStore
    }
}

inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
    val viewModelKey = VIEWMODEL::class
    individualModelStores[viewModelKey]?.clear()
    individualModelStores.remove(viewModelKey)
}

}

使用getSharedViewModel()获取绑定到Activity生命周期的ViewModel实例:

val viewModelCDE : ViewModelCDE = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)

稍后,当处理共享的ViewModel时,请使用clearIndividualViewModelStore<>()

(requireActivity() as MainActivity).clearIndividualViewModelStore<ViewModelCDE>()

在某些情况下,如果不再需要ViewModel,则需要尽快清除ViewModel(例如,如果它包含一些敏感的用户数据,如用户名或密码)。这是一种在每个片段切换时记录individualModelStores状态的方法,以帮助您跟踪共享的ViewModel:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (BuildConfig.DEBUG) {
        navController.addOnDestinationChangedListener { _, _, _ ->
            if (individualModelStores.isNotEmpty()) {
                val tag = [email protected]
                Log.w(
                        tag,
                        "Don't forget to clear the shared ViewModelStores if they are not needed anymore."
                )
                Log.w(
                        tag,
                        "Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${[email protected]}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.