当前位置: 首页 > 知识库问答 >
问题:

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

池麒
2023-03-14

viewmodel随活动或其所连接的片段而生或死。这产生了某些后果,我无法理解为什么没有人询问(如果我们将导航架构融入图片中)。

根据最新的android博客和导航框架的工作方式,建议我们进入单活动多片段诗句。

假设我有以下应用程序设计。

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应该被销毁,但它不会被销毁,因为它与活动相连。

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

事实上,只有我提出这个问题,这使我相信我的理解是错误的。如果我能正确了解情况,我会很高兴。

共有3个答案

巫马令
2023-03-14

我假设这是你的问题:

就像我从A遍历到E,现在返回时弹出一个片段到另一个片段B一样,viewmodel CDE应该被销毁,但它不会被销毁,因为它与活动相连接。

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

我对此的建议解决方案是:

>

  • 在ViewModel类中创建Destroy Data函数,该函数将通过将ViewModel的值覆盖为空值来销毁其数据,例如“”

    class CDEViewModel : ViewModel() {  
       var dataString: String = ""
    
       fun destroyViewModelData() { // Function that will Destroythe Data
           dataString= ""
       }
    }
    

    现在,只要需要确保ViewModel数据被清除/销毁,就可以在片段中调用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的数据都变成了“空字符串”,这意味着它已被重置/销毁。

    希望这个简单的解决方案能有所帮助。非常感谢。

  • 商飞翮
    2023-03-14

    由于导航2.1.0-alpha02(在导航图2.1.0中稳定),您可以通过navGraphViewModels()创建范围位于导航图级别的视图模型。

    要使ViewModel不附加到活动或单个片段,必须创建一个嵌套的导航图,并在该图的范围内请求ViewModel的实例。这将导致当您在嵌套导航图中时,ViewModel将活动,并且嵌套图中的片段将重用ViewModel的同一实例。

    通过这种方式,您可以有几个嵌套的导航图,每个图都有一个ViewModel实例,该实例将在组成该图的片段之间共享。

    我将遵循您相同的片段和视图模型分布:

    MainActivity (Application Entry Point)
    ----------
    Fragment (A) (Uses SharedViewModelOne) -> navGraphOne
    Fragment (B) (Uses SharedViewModelOne) -> navGraphOne
    Fragment (C) (Uses SharedViewModelTwo) -> navGraphTwo
    Fragment (D) (Uses SharedViewModelTwo) -> navGraphTwo
    

    要实现这一点,您必须遵循以下步骤:

    >

    ...
    apply plugin: 'kotlin-kapt'
    
    android {
        ...
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
    
    dependencies{
        ...
        implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
        kapt 'androidx.lifecycle:lifecycle-compiler:2.2.0'
        implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
        implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
    }
    

    选择将共享同一ViewModel的片段,并将其添加到嵌套的导航图中。为此,请在导航图设计器中选择片段,然后右键单击它们并选择“移动到嵌套图”

    在本例中,我将片段A和片段B添加到navGraphOne,将片段C和片段D添加到navGraphTwo。

    在此处查找有关嵌套导航图的详细信息

    在片段A和片段B中,请求SharedViewModelOne的实例。

    private val modelOne: SharedViewModelOne by navGraphViewModels(R.id.navGraphOne) {
        //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.
        defaultViewModelProviderFactory
    }
    
    override fun onCreateView(
        ..
    ): View? {
        ...
        //use binding.lifecycleOwner = viewLifecycleOwner
        //to make sure the observer disappears when the fragment is destroyed
        modelOne.item.observe(viewLifecycleOwner, Observer {
            //do Something
        })
        ...
    }
    

    在片段C和片段D中,请求SharedViewModelTwo的实例。

    private val modelTwo: SharedViewModelTwo by navGraphViewModels(R.id.navGraphTwo) {
        //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.
        defaultViewModelProviderFactory
    }
    
    override fun onCreateView(
        ..
    ): View? {
        ...
        //use binding.lifecycleOwner = viewLifecycleOwner
        //to make sure the observer disappears when the fragment is destroyed
        modelTwo.item.observe(viewLifecycleOwner, Observer {
            //do Something
        })
        ...
    }
    

    然后要验证只创建了ViewModel的单个实例并且它在片段之间共享,请覆盖onCleared()方法并在ViewModel的init{}中添加检查点。

    例如:

    class SharedViewModelOne : ViewModel() {
    
        private val _item = MutableLiveData<String>()
        val item : LiveData<String>
            get() = _item
    
        init {
            Log.d(TAG, "SharedViewModelOne has created!")
        }
    
        override fun onCleared() {
            super.onCleared()
            Log.d(TAG, "SharedViewModelOne has removed!")
        }
    }
    

    遵循前面的步骤后,您应该能够创建一个ViewModel,该ViewModel将在属于同一嵌套导航图的片段之间共享。ViewModel仅在您位于图内时有效,如果您离开它,它将被销毁。

    如果您觉得有些事情不是很清楚,您可以查看此回购并澄清您的疑虑。

    鞠宏恺
    2023-03-14

    这确实是一个问题,并已向谷歌报告。

    幸运的是,自从导航2.1.0-alpha02(在2.1.0中稳定)以来,这个问题已经解决了。您可以在此处找到更改日志和文档。

    现在,您可以通过Kotlin用户的navGraphViewModels()属性委托或使用添加到NavController的getViewModelStore()API创建在导航图级别范围内的视图模型。

    首先,您应该在导航图设计器中选择一些片段,然后右键单击它们并选择“移动到嵌套图”以创建一个新的图,该图将用作如下“范围”:

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

    您可以在此处了解有关嵌套图的更多信息。

     类似资料:
    • 每个应用都会有自己的导航,为了让每个应用都能很方便共享自己的导航数据,我们需要一种良好的应用间导航共享机制,而这种机制就是程序内部可以访问的内部 api. 导航配置文件 导航配置文件就是用于配置应用有哪些导航api,此文件会返回一个数组,数组的子项就一个应用内部 api 链接的地址,但不用带应用名; 导航配置文件是位于应用根目录下的nav.php文件,以 portal 应用为例,就是 app/po

    • 每个应用都会有自己的导航,为了让每个应用都能很方便共享自己的导航数据,我们需要一种良好的应用间导航共享机制,而这种机制就是程序内部可以访问的内部 api. 导航配置文件 导航配置文件就是用于配置应用有哪些导航api,此文件会返回一个数组,数组的子项就一个应用内部 api 链接的地址,但不用带应用名; 导航配置文件是位于应用根目录下的nav.php文件,以 portal 应用为例,就是 app/po

    • 当我试图在我的Android应用程序中处理进程死亡时,我注意到范围为导航图的ViewModel在从进程死亡重新创建时崩溃。下面是使用ViewModel的ViewModel和片段的代码。 在我的片段中,我得到了如下引用 现在,当我试图通过在模拟器中启动应用程序,将其放在后台,然后用android工作室的“终止应用程序”按钮杀死进程来测试应用程序的进程死亡时,它在重新创建时崩溃,并出现以下错误 然而,

    • 我正在一个新的Android应用程序上使用导航组件,但我不知道怎么做 首先,我有我的主活动,我有main_navigation_graph 主要活动 NavHostFragment main_navigation_graph里面有3个碎片 这里一切都很好。问题是当我到达最后一个片段时,因为在这个片段上,我想根据BottomNavigationView输入(暂时)显示一些子片段(在新的NavHost

    • 我有两个屏幕链接到两个标签按钮 < li >名称 < li >持续时间 单击“下一步”按钮时,我希望选项卡移动到第二个按钮。在我的视图模型中,这是我对“下一步”按钮的命令 这是我的代码示例 第二个视图将全屏打开,而不是移动到下一个选项卡。 屏幕已连接 第一屏 第二屏幕 我看到的屏幕

    • 例如,以下结果结构将是理想的: -只读接口 -可变实现 或者更好的是,有没有一种不同的方式来实现这一点?