如何将视图模型范围限定为父片段?

How to scope a view model to a parent fragment?

所以我正在使用新的导航组件(使用一个 activity 原则)并使用共享视图模型在每个片段之间进行通信,但是,我已经到了有时需要清除的地步视图模型,但我找不到清除它的好地方。但老实说,我认为与其自己尝试清除它,不如让框架为我做这件事,但这并不是因为视图模型是共享的,并且范围限定为 activity,但我想我可以将它们的范围限定为父片段,我画了一张图来说明我正在尝试做什么。 所以我只想在从当前视图 "Child 1 Child a" 导航回来时清除 2 个视图模型模型永远不会被清除,试图通过在片段中调用 'this' 来获取当前的视图模型,并且在子项中调用 getParentFragment 不起作用,任何人都可以提供一个例子吗?

编辑

看起来我已经在做类似的事情,但它在我的情况下不起作用,所以我将添加一些代码,这是我如何访问父片段中的第一个视图模型

model = ViewModelProviders.of(this).get(RequestViewModel.class);

然后在子片段中,我这样做

requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);

但它并没有在它们之间保留数据,它们都附加了观察者

好的,所以在 parent

中使用它
model = ViewModelProviders.of(this).get(RequestViewModel.class);

这在 child

requestViewModel = ViewModelProviders.of(getParentFragment()).get(RequestViewModel.class);

给了我不同的哈希码,但 ID 相同,这似乎是因为导航组件,如果我将它们都更改为 getParentFragment 那么它就可以工作,所以我认为该组件正在替换片段而不是在此处添加它们,非常感谢@WadeWilson 和@JeelVankhede

因此,根据 @martin 得出即使 One/Many 片段添加为 Child父片段 中,导航组件 为两个片段提供相同的 片段管理器

这意味着即使将片段添加为父子层次结构,它们也会共享来自导航组件的相同片段管理器(可能是此库中的错误?) & 因此ViewModels 由于在子片段中对 ViewModelProvider 使用 getParentFragment() 实例时的这种困境而未共享。


因此,共享 ViewModels 的一个快速解决方案是从片段管理器获取父片段的实例,对父片段和子片段使用以下行:

ViewModelProviders.of(getParentFragment()).get(SharedViewModel.class); // using this in both parent amd child fragment would do the trick !

Google 让我们现在能够将 ViewModel 的范围限定到导航图。如果您已经在使用导航组件,则可以使用它。 (个人意见,如果你还没有使用它,你应该移动到导航组件,因为它很容易使用。从一个试图自己管理后台堆栈的人那里得到它)

您可以select所有需要在导航图中组合在一起的片段和right-click->move to nested graph->new graph

现在这会将 selected 片段移动到主导航图中的嵌套图,如下所示:

<navigation app:startDestination="@id/homeFragment" ...>
    <fragment android:id="@+id/homeFragment" .../>
    <fragment android:id="@+id/productListFragment" .../>
    <fragment android:id="@+id/productFragment" .../>
    <fragment android:id="@+id/bargainFragment" .../>
    
    <navigation 
        android:id="@+id/checkout_graph" 
        app:startDestination="@id/cartFragment">

        <fragment android:id="@+id/orderSummaryFragment".../>
        <fragment android:id="@+id/addressFragment" .../>
        <fragment android:id="@+id/paymentFragment" .../>
        <fragment android:id="@+id/cartFragment" .../>

    </navigation>
    
</navigation>

现在,在初始化 ViewModel 时在片段中执行此操作

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

如果你需要传递视图模型工厂(可能是为了注入视图模型)你可以这样做:

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }

确保它是 R.id.graph 而不是 R.navigation.graph

出于某种原因,创建导航图并使用 include 将其嵌套在主导航图中对我不起作用。可能是一个错误。

来源:https://medium.com/androiddevelopers/viewmodels-with-saved-state-jetpack-navigation-data-binding-and-coroutines-df476b78144e

谢谢, 为我指明了正确的方向。

在您的应用中使用 Fragment-ktx libr,您可以获得如下视图模型 在您的应用程序中-> build.gradle

 implementation 'androidx.fragment:fragment-ktx:1.1.0'

// 获取 ParentFragment 中的 ViewModel as

 private val viewModel: DemoViewModel by viewModels()

// 在 ChildFragment 中获取与 ViewModel 相同的实例

 private val viewModel: DemoViewModel by viewModels(
    ownerProducer = { requireParentFragment() }
)

使用 Kotlin 和惰性初始化的版本(又称 by viewModels

在父片段中(例如 ViewPager2)

private val viewModel: MyViewModel by viewModels({ this }) {
    TODO("MyViewModelFactory instance")
}

在子 Fragment 中(例如 ViewPager2 中的页面)

private val viewModel: MyViewModel by viewModels({ requireParentFragment() })

如果您使用的是导航组件 (https://developer.android.com/guide/navigation), 您需要以这种方式获取 viewModel:

在您的应用中实施 fragment-ktx -> build.gradle:

implementation 'androidx.fragment:fragment-ktx:1.2.5

在父片段中:

private val viewModel by viewModels<ParentFragmentViewModel>()

在子片段中

private val viewModel by viewModels<ParentFragmentViewModel>({requireGrandParentFragment()})

requireGrandParentFragment() 是 Fragment 的自定义扩展:

fun Fragment.requireGrandParentFragment() = this.requireParentFragment().requireParentFragment()

您需要向上两层访问 parentFragment 的 viewModel 的原因是因为导航组件上 childFragment 的第一个父级是 NavHostFragmentNavHostFragment 是 viewModel 所在的 parentFragment

如果您没有使用导航组件,您可以在 childFragment 中像这样访问它:

private val viewModel by viewModels<ParentFragmentViewModel>({requireParentFragment()})

我在所有解决方案中都遇到了同样的问题,但在我的场景中没有工作想出另一个解决方案,使用 requireParentFragment() return NavHostFragment in child fragment 通过使用

解决它
private val viewModel: childViewModel by viewModels(
            ownerProducer = { requireParentFragment().childFragmentManager.primaryNavigationFragment!! }
    )

在Parent Fragmet中使用这个

private val viewModel: MyOrdersVM by viewModels()

我做错的是向 DialogFragment 提供了错误的片段管理器。 所以在我的例子中它是这样工作的:

class MyDialog : DialogFragment() {

    private val viewModel: MyViewModel by viewModels({ requireParentFragment() })

并在我的 fragment 中初始化对话框:

private fun showDialog(){
    MyDialog().show(childFragmentManager, "AddFriendToGroupDialog")
}

我正在使用 implementation 'androidx.fragment:fragment-ktx:1.3.0' 和导航图。

我也有这个问题。为子片段创建了一个新的 ViewModel。 问题是 getParentFragment() returns NavHostFragment 而不是所需的片段。

并且我们需要在子片段中获取真正的父对象。

如果我们这样做 requireParentFragment().requireParentFragment(),那么我们就会得到真正的父级。

解决方法:(子更新)

Fragment parent = requireParentFragment().requireParentFragment();
viewModel = new ViewModelProvider(parent).get(RequestViewModel.class);

Found a solution here