'Android - mixing NavGraph with ViewPager2

I'm looking for the following design in my android app:

I want the main screen to be a TabLayout with 3 tabs, and a ViewPager2. From each one of the 3 fragments (Fragment1, Fragment2, Fragment3) corresponding to the tabs, there are some actions the user can do to navigate to other fragments--those don't have the TabLayout and ViewPager2. Those nav actions are handled by a nav_graph.

Currently, this is what I'm working with:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/main_activity_coordinator"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical">
    <com.google.android.material.appbar.AppBarLayout
        app:layout_constraintTop_toTopOf="parent"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:id="@+id/app_bar"
    >
        <!-- toolbar -->

    </com.google.android.material.appbar.AppBarLayout>
    

    <androidx.fragment.app.FragmentContainerView
        android:tag="MAIN-FRAG"
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="70dp"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

main_fragment.xml (this contains the TabLayout and ViewPager2)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:tag="MAIN-FRAG"
    android:id="@+id/main_relative_layout"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:paddingTop="?attr/actionBarSize"
    xmlns:tools="http://schemas.android.com/tools">
    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    >

        <com.google.android.material.tabs.TabItem
            ...
        />

        <com.google.android.material.tabs.TabItem
            ...
        />

        <com.google.android.material.tabs.TabItem
            ...
        />

    </com.google.android.material.tabs.TabLayout>

        <androidx.viewpager2.widget.ViewPager2
            android:layout_below="@id/tabBar"
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
        />

</RelativeLayout>

MainFragment.java

public class MainFragment extends Fragment {
    private ViewPager2 viewPager;
    private NavigationAdapter navigationAdapter;
    private MainFragmentBinding binding;


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getActivity().setContentView(R.layout.main_fragment);
        TabLayout tabLayout = getActivity().findViewById(R.id.tabBar);
        int[] tabTexts = {
                R.string.tab1,
                R.string.tab2,
                R.string.tab3
        };

        // set up view pager and attach mediator to tab layout
        this.viewPager = getActivity().findViewById(R.id.pager);
        this.navigationAdapter = new NavigationAdapter(this);
        viewPager.setAdapter(this.navigationAdapter);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText(
                        tabTexts[position]
                )
        ).attach();
    }
}

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<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_graph"
    app:startDestination="@id/Fragment1">

    <fragment
        android:id="@+id/Fragment1"
        android:name="com.my_app..fragments.MainFragment"
        tools:layout="@layout/main_fragment">

        <action
            android:id="@+id/action_Fragment1_to_FragmentX"
            app:destination="@id/FragmentX" />
    </fragment>
    <fragment
        android:id="@+id/FragmentX"
        android:name="com.my_app.fragments.FragmentX"
        tools:layout="@layout/fragment_x">
    </fragment>


    <fragment
        android:id="@+id/Fragment2"
        android:name="com.my_app.fragments.ExercisesFragment"
        tools:layout="@layout/fragment_2">

        <action
            android:id="@+id/action_Fragment2_to_FragmentY"
            app:destination="@id/FragmentY" />
    </fragment>
    <fragment
        android:id="@+id/FragmentY"
        android:name="com.my_app.fragments.ExerciseDetailFragment"
        tools:layout="@layout/fragment_y">
    </fragment>
</navigation>

In other words, the TabLayout handles Fragment1, Fragment2, and Fragment3 (not shown in the nav graph because it doesn't have outward actions). From Fragment1, you can navigate to FragmentX (which doesn't not have the tablayout) and from Fragment2 to FragmentY.

This seems to be working, but if I rotate my screen I get the following error:

2022-05-06 11:09:00.009 11445-11445/com.my_app E/FragmentManager: Fragment no longer exists for key f#0: unique id ba9898fb-18f8-4eaa-a250-97c08a896379
2022-05-06 11:09:00.009 11445-11445/com.my_app E/FragmentManager: Activity state:
2022-05-06 11:09:00.031 11445-11445/com.my_app E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.my_app, PID: 11445
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.my_app/com.my_app.MainActivity}: java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id ba9898fb-18f8-4eaa-a250-97c08a896379
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2827)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2902)
        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4784)
        at android.app.ActivityThread.-wrap18(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1609)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:169)
        at android.app.ActivityThread.main(ActivityThread.java:6578)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
     Caused by: java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id ba9898fb-18f8-4eaa-a250-97c08a896379
        at androidx.fragment.app.FragmentManager.getFragment(FragmentManager.java:975)
        at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:549)
        at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:350)
        at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:375)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.View.restoreHierarchyState(View.java:17620)
        at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2130)
        at android.app.Activity.onRestoreInstanceState(Activity.java:1122)
        at android.app.Activity.performRestoreInstanceState(Activity.java:1077)

I believe this is somehow tied to the fact that my nav_graph uses one of the fragments managed by the ViewPager2 as its start destination. However, if I try and use the MainFragment as my start destination, then I can't navigate to FragmentX and FragmentY, because if I do, I'll get an error saying the action isn't valid when I'm in MainFragment.

How can I use both ViewPager2 and NavGraph correctly?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source