【问题标题】:Get parent fragment from child when using Navigation Component使用导航组件时从子级获取父级片段
【发布时间】:2020-03-01 13:31:31
【问题描述】:

我需要将数据从一个片段传输到另一个片段。现在推荐的方法是使用共享的ViewModel。要在两个片段中获得相同的实例,需要共同的所有者。因为它可以是他们共同的Activity。但是使用这种方法(在 Single Activity 的情况下),ViewModel 实例将存在于整个应用程序中。在片段的经典使用中,可以在父片段中指定ViewModelProvider (this),在子片段中指定ViewModelProvider (getParentFramgent ())。因此,ViewModel 的范围仅限于父片段的生命周期。问题是在使用导航组件时,getParentFramgent () 将返回 NavHostFragment,而不是父片段。我需要做什么?

代码示例

navigation_graph.xml 中的某处:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav"
        app:startDestination="@id/mainMenuFragment">

    <fragment
        android:id="@+id/mainMenuFragment"
        android:name="com.mypackage.mainmenu.MainMenuFragment"
        android:label="MainMenu"
        tools:layout="@layout/fragment_main_menu">

        <action
            android:id="@+id/start_game_fragment"
            app:destination="@id/gameNav" />

    </fragment>

    <navigation
        android:id="@+id/gameNav"
        app:startDestination="@id/gameFragment">

        <fragment
            android:id="@+id/gameFragment"
            android:name="com.mypackage.game.GameFragment"
            android:label="@string/app_name"
            tools:layout="@layout/fragment_game"/>
    </navigation>

</navigation>

MainMenuFragment 某处:

    override fun startGame(gameSession: GameSession) {
            //This approach doesn't work
            ViewModelProvider(this)[GameSessionViewModel::class.java].setGameSession(
                gameSession
            )
            findNavController().navigate(R.id.start_game_fragment)
    }

GameFragment

    override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            gameSessionViewModel =
                ViewModelProvider(requireParentFragment())[GameSessionViewModel::class.java].apply {
                    val session = gameSession.value
                    )
                }
    }

编辑:

我可以使用NavHostFragment(从getParentFragment()返回)作为所有片段的公共,但是,在Activity的情况下,ViewModel.onCleared()不会在真正的父片段完成时被调用。

【问题讨论】:

  • 能否贴出添加子片段的代码
  • @FahadAlotaibi,只需 findNavController().navigate(R.id.start_game_fragment)
  • 所以,NavHostFragment 将是父片段,所有片段都将添加为子片段
  • @FahadAlotaibi,是的,这个 NavHostFragment 对所有片段都是通用的。它在活动中被实例化。它会在活动存在时继续存在。
  • @FahadAlotaibi,比如我用一个Fragment,一个ViewModel的方式,当fragment结束时,ViewModel会调用onCleared()

标签: android android-fragments android-viewmodel


【解决方案1】:

真的没有办法做到这一点。

这是来自androidx.navigation.fragment.FragmentNavigator的代码sn-p:

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                    @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
    // ...
    final FragmentTransaction ft = mFragmentManager.beginTransaction();
    // ...
    ft.replace(mContainerId, frag);
    ft.setPrimaryNavigationFragment(frag);
    // ...
}

在底层使用FragmentManager,它调用replace()。所以子fragment没有被添加,而是被一个新的替换,所以不会在getParentFramgent()中。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,经过研究和实验,我找到了解决方案。

    您只需在确定 ViewModel 的范围时调用它,这将解析为您创建导航主机片段的片段。

    ViewModelProvider(getParentFragment().getParentFragment())
    

    或者更恰当

    ViewModelProvider(requireParentFragment().requireParentFragment())
    

    (避免 NPE)

    这是因为子 Fragment 的父级是 NavHostFragment,而 NavHostFragment 的父级是 ParentFragment。

    我对此进行了测试,对我来说效果很好

    【讨论】:

    • 这对我不起作用:(父母的父母对我来说是空的..
    【解决方案3】:

    对于那些被这个问题困扰的人,这里是对我有用的答案:

    假设你从fragment A 到fragment B,并且你想为fragment A 和B 使用一个共享的ViewModel,在这种情况下,A 是一个fragment,B 是一个dialogFragment。

    您可以在片段 A 中初始化 ViewModel:

    ViewModelProvider(this).get(AviewModel::class.java)
    

    为了在片段 B 上使用它,请像这样初始化片段 B 中的 ViewModel:

    ViewModelProvider(requireParentFragment().childFragmentManager.fragments[0]).get(AviewModel::class.java)
    

    TL;DR:

    requireParentFragment() 返回NavHostFragment,它承载了当前导航的所有导航。

    requireParentFragment().childFragmentManager 返回FragmentManagerImpl,这似乎是所有片段的容器。

    requireParentFragment().childFragmentManager.fragments 返回堆栈中的可变片段列表,因此您可以遍历该列表以查看堆栈中的片段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-14
      • 1970-01-01
      • 2023-02-09
      • 2021-11-01
      • 2017-01-04
      相关资源
      最近更新 更多