1. How to correctly use Navigation

I believe everyone has heard of Navigation, I won’t go into details about how to use it, the official explanation is also very detailed. I want to talk about how to use this component better.

In fact, this component needs to be used with the official MVVM architecture, and the combination of ViewModel and LiveData can better demonstrate the advantages of Navigation.

ViewModel and LiveData are not used in the official demo. The official demo just shows how Navigation works, how to pass values between pages, and some of the features of this component. But really good use is to combine ViewModel and LiveData.

2. Navigation is a perceived defect

At first, when I used Navigation, the biggest headache was that when I pressed the back button to return to the previous page, the whole page was rebuilt, which was an unwanted result in development. Most of the time, people would seek a way to replace the official replace mode with Hide and Show. Is also thought of that way at first, and then combined with information on the Internet to get their FragmentNavigatorHideShow wrote a way.

3. But this is not a flaw

But soon, I realized that this method (Hide and Show) had serious logic problems.

As you can see here, there are some scenarios where we have a page that opens the same page as our own, but displays different data. When I display the next page with hide and show, I find that the previous page is still open. When you press the return key, the same page disappears, and the new page is the same object as the previous page, which is definitely not business logic. Therefore, I began to study the method of replace. Of course, I used MVVM + ViewModel+LiveData when using this Navigation. At that time, I remembered that ViewModel was not affected by Fragment reconstruction. So I printed out what the page life cycle would look like with replace.

From HomeFragment to MyFragment lifecycle changes:

So you can see that after replace, instead of executing onDestory, the HomeFragment executed onDestoryView and that made the page have to be rebuilt. OnDestoryView doesn’t cause the ViewModel to be destroyed. That is, the ViewModel is still there, and the data stored by LiveData in the ViewModel is still there. When I press the return key, I return to the HomeFragment page and execute onViewCreated as a matter of course. At this time, the page in the code observes the LiveData observed in the ViewModel again. Since LiveData saved the data before, this code should be executed as well. The page is also repopulated with data.

    override fun initLiveData(a) {
        viewModel.liveData.observe(this) {
            Log.d(TAG, "data change :  $it ")
            textView.text = it
        }
    }
Copy the code

At this point, you will notice that the page does not appear to have been rebuilt. That’s when I understood what Google was driving at. What a clever move he made.

Also, of course, in the one I abandoned the FragmentNavigatorHideShow, hug back to Google dad again.

To get back to the above issue, when a page can open itself, the FragmentNavigator source code creates a new Fragment whenever it navigates to the next destination, and the previous Fragment is added to the rollback stack. So you can open a new version of yourself in the Fragment to display different information. The hide and show methods check each time to see if the page has been created before, show if it has, and create if it has not. That’s why you open yourself, always the same Fragment object.

4. So how to use it correctly

How to correctly use Navigation is also my little experience in this period of time.

All dynamic data in the Fragment is stored by LiveData in the ViewModel. We only listen for changes in LiveData, which is consistent with MVVM architecture, and of course there is a Model I didn’t say Repository, which I won’t explain.

Any data that is passed between fragments will be stored in the Bundle when it is rebuilt, and it is perfectly safe to go through the process of retrieving data from the Bundle. So the data on the page will not be lost, and RecyclerView, ViewPager and so on they will also save their previous state, after the page is rebuilt, RecyclerView, ViewPager will record their sliding position, don’t worry about this, there is another point is that there are some controls, For example, CoordinatorLayout you might have to give it and its child View controls an Id in order to save the slide state.

After following such a rule, you can ignore the problem of page reconstruction.

5. Some problems of Navigation page transition animation

As anyone who has used Navigation knows, page transitions should be added one by one, like this:

<! -- This is the official Demo -->
<fragment
            android:id="@+id/title_screen"
            android:name="com.example.android.navigationsample.TitleScreen"
            android:label="fragment_title_screen"
            tools:layout="@layout/fragment_title_screen">
        <action
                android:id="@+id/action_title_screen_to_register"
                app:destination="@id/register"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
        <action
                android:id="@+id/action_title_screen_to_leaderboard"
                app:destination="@id/leaderboard"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"/>
    </fragment>
Copy the code

I had to write the same code for every tag, and it was really annoying. So I came up with the idea of overwriting the FragmentNavigator to add a judgment to everything and if there’s no special animation in the tag, then I’ll just add a special animation to that Fragment.

      	// The animation operation I originally envisioned adding the source code location
		intenterAnim = navOptions ! =null? Navoptions.getenteranim () : animation ID;intexitAnim = navOptions ! =null? Navoptions.getexitanim () : animation ID;intpopEnterAnim = navOptions ! =null? NavOptions. GetPopEnterAnim () : animation id;intpopExitAnim = navOptions ! =null? NavOptions. GetPopExitAnim () : animation id;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);
        }
Copy the code

However, I was naive, we thought of, Google dad thought of everything. Because if you add that naively, as I did, you’ll see that the first default Fragment also has an animation property. And when doing implicit link jumps, this animation is very bad for the look and feel. So the first default Fragment can’t have transitions. Of course, then I came up with the idea of checking if the return stack is empty, by checking if it’s the first page. But I can think of it all and So can Dad Google. There must be a reason they don’t. Or wait for the official optimization, so I gave up, honest copy and paste,

But later I found this problem in Navigation issues, because it should be in the optimization plan.

6. Replace overanimation freezes when rebuilding fragments

When using Navigation, press the back button to return to the previous page, and the page is rebuilt, it will be found that the transition animation will be delayed for several hundred milliseconds, and a transition animation is only about 400 milliseconds, so the effect is very obvious. This is also due to the Fragment reconstruction, when the amount of data displayed on the page is very large, the amount of drawing work during the reconstruction is also quite large, so it will definitely be a bit slow.

Then I found a way:

    override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
        return super.onCreateAnimation(transit, enter, nextAnim)
    }
Copy the code

We can request the data loading process after the animation is executed.

    override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
       if (enter) {
           if (nextAnim > 0) {
               val animation = AnimationUtils.loadAnimation(requireActivity(), nextAnim)
               animation.setAnimationListener(object : Animation.AnimationListener {
               
                   override fun onAnimationEnd(animation: Animation?). {
                       onEnterAnimEnd()// Request network data or initialize LiveData after the animation ends}})return animation
           } else {
               onEnterAnimEnd()
           }
       } else {
           if (nextAnim > 0) {
               return AnimationUtils.loadAnimation(requireActivity(), nextAnim)
           }
       }
       return super.onCreateAnimation(transit, enter, nextAnim)
   }
   
   /** * subclass to determine whether to load data or initialize LiveData */
   fun onEnterAnimEnd(a){
       Log.d(TAG, "onEnterAnimEnd: ")}Copy the code

And then we’re going to find the onViewCreated method, because the Base class usually abstracts the initialization method so we’re going to do two things:

1: Pause the cutscene while the View is drawing initialization

2: Start animation execution after View and Data initialization


    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
       super.onViewCreated(view, savedInstanceState)
       // Pause the cutscene
       postponeEnterTransition()
       //View and data initialization
       initViewAndData(view)
       initLiveData()// Initialization of LiveData can be placed after the animation ends
        // Finally use this method to listen on the view structure and start executing the cutscene
       (view.parent as? ViewGroup)? .apply { OneShotPreDrawListener.add(this){
               startPostponedEnterTransition()
           }
       }
   }

Copy the code

After this operation can prevent the problem of changing frames caused by RecyclerView when a large number of data is loaded, but it is not completely not to drop frames, but this solution is still effective, the rest is to optimize their own code, to prevent too many time-consuming operations.

My previous solution was to delay the execution of the cutscene by 100 milliseconds, but I was not satisfied with this method, so I still consulted the official documents to find a solution. Digg friends also raised relevant problems for me. I am not busy these days, so I have to revise the problems I made before.

Other recommended

LiveData is nicely packaged and I’m not afraid of Navigation reconstruction Fragments anymore! This article is supplemented by the use of LiveData