'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 |
---|