使用导航组件时,从子级获取父级片段

时间:2020-03-01 13:31:31

标签: android android-fragments android-viewmodel

我需要将数据从一个片段传输到另一个片段。现在,推荐的方法是使用共享的ViewModel。为了在两个片段中获得相同的实例,需要公共所有者。因为这可能是他们共同的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()将不会被调用当真正的父片段完成时。

3 个答案:

答案 0 :(得分:1)

真的没有办法。

这是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()。因此,不会添加子片段,而是将其替换为新的子片段,因此它将不在getParentFramgent()中。

答案 1 :(得分:1)

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

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

ViewModelProvider(getParentFragment().getParentFragment())

或更恰当地

ViewModelProvider(requireParentFragment().requireParentFragment())

(避免NPE)

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

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

答案 2 :(得分:0)

有一种简单的方法可以做到这一点。您可以像这样在子片段中获取父级的 viewModel

private val parentViewModel: ParentViewModel by viewModels ({
    parentFragmentManager.fragments.first { fragment -> fragment is ParentFragment }
})

但请确保您的 ParentFragment 实际上在 backstack 中。

相关问题