Samuele B.
Samuele B.

Reputation: 658

Android ViewPager 2 - Fragment no longer exists for key

I have an app in which a main fragment contains a tab layout tied to a ViewPager2 in order to navigate between three different fragments. When I rotate my phone's screen, the app crashes with this error:

Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820 (complete error stack at the end of the post)

This is my setup:

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"
        android:theme="@style/Theme.VirtualGymBuddy.AppBarOverlay"
    >

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:title="@string/app_name" />


    </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"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

main_fragment.xml

<?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"
        app:tabTextAppearance="@style/MyTabLayoutStyle"
        >

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab1" />

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab2" />

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab3" />

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

content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/content_main_constraint"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <fragment
        android:id="@+id/nav_host_fragment_content_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

ActivityMain.java

public class MainActivity extends AppCompatActivity {
    private AppBarConfiguration appBarConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(null);
        setContentView(R.layout.activity_main);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        return NavigationUI.navigateUp(navController, appBarConfiguration)
                || super.onSupportNavigateUp();
    }

}

MainFragment.java

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

    @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.tab_1,
                R.string.tab_2,
                R.string.tab_3
        };

        // 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();
    }
}

NavigationAdapter.java

public class NavigationAdapter extends FragmentStateAdapter {


    public NavigationAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
    }

    public NavigationAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    public NavigationAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
        super(fragmentManager, lifecycle);
    }


    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position) {
            case 0:
                return new Fragment1();
            case 1:
                return new Fragment2();
            case 2:
                return new Fragment3();

        }
        throw new AssertionError();
    }

    @Override
    public int getItemCount() {
        return 3;
    }
}

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/MainFragment">

    <fragment
        android:id="@+id/MainFragment"
        android:name="com.myapp.fragments.MainFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/main_fragment" />
</navigation>

Everything works fine and I can navigate through the fragments handled by ViewPager2.

However, as soon as I rotate the screen inside of one of those fragments, I get this:

2022-05-08 00:08:12.339 12343-12343/com.myapp E/FragmentManager: Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820
2022-05-08 00:08:12.339 12343-12343/com.myapp E/FragmentManager: Activity state:
2022-05-08 00:08:12.358 12343-12343/com.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.myapp, PID: 12343
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp/com.myapp.MainActivity}: java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820
        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 c4576c9c-4bbd-4dc0-a304-b86653ed2820
        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)
        at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1260)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2800)
        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) 

What's wrong with my setup?

Upvotes: 4

Views: 3393

Answers (1)

Rajalakshmi Arumugam
Rajalakshmi Arumugam

Reputation: 389

There is an issue with viewpager2 detach from recyclerview https://issuetracker.google.com/issues/154751401

As per my knowledge two fixes are there:

  1. add this line in your xml viewpager2 component.

       <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"
        android:saveEnabled="false">
    

But the above one don't save the current instance, which the fragment will recreate each time it pops back.

  1. The recommended solution is add the below line on your mainfragment

     override fun onDestroyView() {
        super.onDestroyView()
        viewPager.adapter = null
    }
    

Upvotes: 4

Related Questions