【问题标题】:How to scope a view model to a parent fragment?如何将视图模型范围限定为父片段?
【发布时间】:2019-05-17 22:15:42
【问题描述】:

所以我正在使用新的导航组件(具有单一活动原则)并使用共享视图模型在每个片段之间进行通信,但是,我已经到了有时需要清除视图模型但我可以找不到清理它的好地方。但是,我认为与其自己尝试清除它,不如让框架为我做这件事,但这并不是因为视图模型是共享的并限定于活动,但我认为我可以将它们限定为一个父片段,我画了一张图来说明我正在尝试做的事情。 所以当我从“Child 1 Child a”导航回来时,我只想清除 2 个视图模型,目前视图模型永远不会被清除,试图通过在片段中调用“this”来获取当前视图模型,而在孩子中调用 getParentFragment 不起作用,谁能提供一个例子?

编辑

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

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

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

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

但它不会在它们之间保留数据,它们都附有观察者

【问题讨论】:

  • 您可以参考这个答案stackoverflow.com/a/52732831/10271334,如果有任何困惑,请告诉我。
  • 好的,这就是我已经尝试过的,让我添加一些代码
  • 我不确定我是否遗漏了什么,但您完全可以使用特定片段实例注册 ViewModel,因此您不必担心它会被片段返回。
  • 如果要共享Viewmodel,则传入的对象引用必须相同。我认为您可能需要确保将相同的实例传递给 .of(...) 调用。调试或修改 toString 方法以返回唯一值并注销您在父片段和子片段中传递给 ViewModelProviders.of(...) 的值。
  • 您能否尝试在父子片段中打印或调试您共享的ViewModels 的哈希码,以验证它们是否已共享?如果它们的含义相同,则它们是共享的..还要检查您的孩子的孩子片段。这可能是原因。

标签: android android-fragments viewmodel android-architecture-navigation


【解决方案1】:

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

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

// 获取 ParentFragment 中的 ViewModel 为

 private val viewModel: DemoViewModel by viewModels()

// 在 ChildFragment 中获取与

相同的 ViewModel 实例
 private val viewModel: DemoViewModel by viewModels(
    ownerProducer = { requireParentFragment() }
)

【讨论】:

  • 目前存在一个错误,当您将刀柄绑定到导航组件时,您需要指定默认视图模型工厂,否则这将是事实上的方法
  • @martinseal1987 我已根据您的要求更新了更改。 viewModels 导入 androidx.fragment.app.viewModels 这是 Fragment-ktx 的一部分。谢谢
【解决方案2】:

好的,所以在父级中使用它

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

这在孩子身上

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

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

【讨论】:

  • 在观察部分,我们作为生命周期所有者发送什么?我得到了Unsafe call to observe with Fragment instance as LifecycleOwner from MyFragment.onViewCreated.
  • 使用 viewLifecycleOwner, pokemonListViewModel.searchPokemon.observe(viewLifecycleOwner, Observer { pokemonList ->
  • 所以我使用作用域视图模型方法:by viewModels 懒惰地创建视图模型。为了在片段和它启动的bottomSheetFragmentDialog之间共享相同的viewModel,我需要在片段和bottomSheetFragment中使用`by viewModels({ requireParentFragment() })。
【解决方案3】:

因此,根据 @martinproposed solution 得出,即使在 Parent Fragment 中添加一个/多个片段作为 导航组件为两个片段提供相同的片段管理器

这意味着即使片段被添加为父子层次结构,它们也会共享来自导航组件的相同片段管理器(可能是这个库中的错误?) & 所以ViewModels 不是由于在子片段内部使用ViewModelProvidergetParentFragment() 实例时出现这种困境,所以共享。


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

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

【讨论】:

  • 如果您将片段声明为 xml 中的片段并在 xml 中为其提供图形,那么该片段将成为父片段,这意味着我可以分离一些导航流程,但不幸的是这有时会破坏您的 popBackStack 调用,因为您最终需要一个容器来保存片段,我讨厌导航组件!因为它是我建议不要使用,因为我认为它已经坏了,但我的公司希望我使用它,因为它应该是事实上的前进方式,再次感谢
  • 是的,实际上建议如果任何库或依赖项处于 alpha 状态,则避免或尽量不要使用它,直到它稳定为止;为了最终的稳定应用。
  • 您可以通过调用 getActivity().getSupportFragmentManager().popBackStack() 来修复返回按钮
  • 如果它是父片段,getParentFragment() 不会返回 null。那样的话,那个方法会抛出异常吗?
  • 不,object 将只是 null。这段代码不会抛出任何异常。
【解决方案4】:

如果您使用的是导航组件 (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

如果你没有使用 Navigation 组件,你可以在 childFragment 中这样访问它:

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

【讨论】:

  • 在这里聚会有点晚了,但我想知道您是否能够使用片段场景对此进行测试?我的测试失败是因为被测片段不是子片段,而是直接附加到 EmptyFragmentActivity 进行测试。我可以伪造两个父片段来解决,但一定有更好的方法吗?
【解决方案5】:

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

您可以在导航图和right-click-&gt;move to nested graph-&gt;new graph内选择所有需要组合在一起的片段

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

<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 时,在 Fragments 中执行此操作

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 将其嵌套在主导航图中对我不起作用。应该是bug吧。

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

感谢Manually clearing an Android ViewModel? 为我指明了正确的方向。

【讨论】:

【解决方案6】:

我在所有解决方案中都尝试过同样的问题,但在我的场景中不起作用使用 requireParentFragment() 在子片段中返回 NavHostFragment 使用其他解决方案解决它

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

在父片段中使用这个

private val viewModel: MyOrdersVM by viewModels()

【讨论】:

    【解决方案7】:

    使用 Kotlin 和延迟初始化的版本 (AKA by viewModels)

    在父 Fragment 中(例如 ViewPager2)

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

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

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

    【讨论】:

      【解决方案8】:

      我做错了向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' 和导航图。

      【讨论】:

        【解决方案9】:

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

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

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

        解决方案:(用于子更新)

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

        Found a solution here

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-03-17
          • 2013-10-30
          • 2015-12-14
          • 2013-10-22
          • 1970-01-01
          • 2018-05-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多