当使用导航组件时,如何从子片段获取父片段?

16
我需要将数据从一个片段传递到另一个片段。现在推荐的方法是使用共享 ViewModel。为了在两个片段中都能使用相同的实例,需要一个公共所有者。这可以是它们的共同 Activity。但是使用这种方法时(在单个 Activity 的情况下),ViewModel 实例将在整个应用程序中存在。在经典的片段用法中,您可以在父片段中指定 ViewModelProvider (this),并在子片段中指定 ViewModelProvider (getParentFramgent ())。因此,ViewModel 的范围仅限于父片段的生命周期。问题是,当使用 Navigation 组件时,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()


你能发布添加子片段的代码吗? - Fahad Alotaibi
@FahadAlotaibi,只需使用findNavController().navigate(R.id.start_game_fragment)即可。 - Ziens
因此,NavHostFragment 将成为父片段,所有片段都将作为子片段添加。 - Fahad Alotaibi
@FahadAlotaibi,是的,这个NavHostFragment对所有片段都是通用的。它在活动中实例化。只要活动存在,它就会存在。 - Ziens
@FahadAlotaibi,例如,如果我使用一片段,一个ViewModel的方法,当片段结束时,ViewModel将调用onCleared()。 - Ziens
@Ziens 也请看一下我的问题 https://dev59.com/w7D3oIgBc1ULPQZFE8z- 和 https://stackoverflow.com/questions/70680327/toolbar-icon-handling-using-navigation-component。 - Taimoor Khan
4个回答

4

很抱歉,这是无法完成的。

这里是来自 androidx.navigation.fragment.FragmentNavigator 的代码片段:

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()方法。因此,子片段并未被添加,而是被替换为新的片段,因此它将不会在getParentFragment()中出现。

2

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

你只需要在ViewModel作用域内调用这个方法,它将会解析到你创建了导航主机片段的地方。

ViewModelProvider(getParentFragment().getParentFragment())

更恰当地说,
ViewModelProvider(requireParentFragment().requireParentFragment())

(避免NPE)

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

我测试过了,对我很有效。


1
这对我没用 :( 对我来说,父级的父级为空。 - Eric

1

如果您遇到了这个问题,以下是我使用的解决方法:

假设您从片段A转到片段B,并且您想要为片段A和B使用共享ViewModel,在这种情况下,A是一个片段,而B是一个对话框片段。

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

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

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

并且为了保留HTML标签,不要删除任何内容。

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

TL;DR:

requireParentFragment()会返回NavHostFragment,该片段托管当前导航的所有内容。

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

requireParentFragment().childFragmentManager.fragments返回一个可变的片段列表,其中包含堆栈中的所有片段,因此您可以通过遍历列表来查看存储在堆栈中的片段。


1
使用以下代码来通过导航组件访问父片段函数:
NavHostFragment navHostFragment = (NavHostFragment) getParentFragment();
ParentFragment parent = (ParentFragment) navHostFragment.getParentFragment();
parent.somePublicFunctin();

1
运作得非常顺利! - Clamorious

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接