After Jetpack's Navigation fallback, the page is destroyed

1, Emerging phenomena
Use Navigation to jump between fragments. When you go back, you find that the data on the previous Fragment page is gone. After investigation, it is found that the page has been re executed onCreateView, that is, the page has been re created. In this way, the experience effect is different from that of previous multiple activities.

2, Analyze the reason
In fact, this is deliberately designed by google. google's original intention is to have a ViewModel in the host Activity. The ViewModel maintains the data in the page. Every time a Fragment is created, it obtains the previous data through the ViewModel. If the page is a ListView, how to maintain the previous state when re creating is more troublesome. So what can be done?

3, Solution
First of all, we need to know why Fragment executes onCreateView when it returns. After analysis, the key lies in the navigate method in the class FragmentNavigator

 @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
       	//Ignore partial code
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }
		//The key is this replace, which will replace the current page every time a new page is opened
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;
		//Ignore partial code

       
    }

After viewing the source code, we can actually replace the current page with the new page opened. We know that we can hide the current page first and then add a new page. However, we can't modify the source code, because we can copy a copy of our own modification and use the class we created when referencing.
We copy the FragmentNavigator class, rename a MyFragmentNavigator, and modify the navigate method

@Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
       	//Ignore partial code
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }
        //First hide the current fragment, and then add a new page
		if(mFragmentManager.getFragments().size()>0){
            ft.hide(mFragmentManager.getFragments().get(mFragmentManager.getFragments().size()-1));
            ft.add(mContainerId, frag);
        }else {
            ft.replace(mContainerId, frag);
        }
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;
		//Ignore partial code
    }

At present, we have created MyFragmentNavigator, but this MyFragmentNavigator cannot be used directly. Through source code analysis, we know that NavHostFragment references FragmentNavigator, so we must also modify NavHostFragment. We only need to copy a NavHostFragment, rename it MyNavHostFragment, and change the referenced FragmentNavigator into our own MyFragmentNavigator.

4, How to use
We know it is written like this in the Activity layout file

<fragment
        app:defaultNavHost="true"
        app:navGraph="@navigation/my_nav"
        android:id="@+id/nav_host_fragment"
        //Just change this to the full path of MyNavHostFragment
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </fragment>

Then we can use it normally. The jump and parameter transfer of the page use the previous code

Summary:
1. Copy the FragmentNavigator to MyFragmentNavigator and modify the navigate method inside
2. Copy NavHostFragment to MyNavHostFragment, and change the internally referenced FragmentNavigator to MyFragmentNavigator
3. Replace the full path of MyNavHostFragment in the main Activity layout file

Tags: Android

Posted by icedude on Sat, 07 May 2022 03:40:39 +0300