This article has been submitted by Qing Mei Sniff.

https://www.jianshu.com/p/ad040aab0e66

Permission to reprint please contact the original author, shall not be reproduced without permission

preface

I try to avoid using and managing fragments in my projects, especially when dealing with multiple nested and fallback fragments. Fortunately, WITH the Activity, I was able to bypass many of the Fragment’s complex usage scenarios — admittedly, the weirdness of the Fragment gave me a headache compared to the simplicity of the Activity.

Of course, there are many predecessor fragments management frameworks on Github. This is the simplest solution, such as Fragmentation Manager, which is popular at present, and our humble Yumenokanata FragmentManager with functional architecture. They’ve been through a number of projects, their frameworks are mature and stable, and they contain design ideas that I think I could never achieve in my entire career.

However, I haven’t tried to use them because of the existence of activities. I don’t think there is enough need to implement multiple fragments in complex scenarios. Simple stack rollback management can be achieved through Android’s native API. Fragment Complex management applications have always been my technical blind spot.

Learning opportunity

At Google 2018 I/O last week, Google officially unveiled Android JetPack — a set of components, tools, and instructions to help developers build great Android apps, This includes Lifecycle, ViewModel, LiveData and Room, which were launched last year. In addition, AndroidJetpack also introduced a new architectural component: Navigation.

From the name, I translated it as Navigation, let’s take a look at Google’s official description of it:

Today, we’re announcing the Navigation component as a framework for building your in-app interface, with a focus on making single-activity apps the preferred architecture. With the Navigation component’s native support for fragments, you can get all the benefits of an architectural component (such as life cycle and ViewModel) while letting this component handle the complexity of fragmentTransactions for you. In addition, the Navigation component allows you to declare transitions we handle for you. It automatically builds correct “up” and “back” behavior, includes full support for deep linking, and provides helper programs for associating navigation with appropriate UI widgets, such as the drawer navigation bar and bottom navigation.

Leaving aside the subject of comparison (StoryBoard VS Navigation?) The release of Navigation made me realize that this is an opportunity, and I think I need to spend time to learn more about it. I can not only learn new technologies and concepts, but also find out what is missing and improve my Android knowledge system (Fragment management).

This matter was immediately put on my agenda. In the past week, I studied Navigation carefully in my spare time and got some insights. I tried to write down this article. If possible, I would even like this article to do:

This article is not a detailed API documentation, but just by reading this article, you will be able to systematically learn about Navigation — understand it, understand it, and finally understand it.

This is a challenge for both readers and readers. The first step to doing this is to know how to use Navigation.

Understand the Navigation

1. Official documents

The official documentation is always the closest reference to a correct and core idea — this article may be rendered meaningless by the iteration of the framework’s own API in the near future, but the official documentation is not, and it remains the most reliable beacon even in the worst of storms:

https://developer.android.com/topic/libraries/architecture/navigation/

Secondly, a good Demo can be an important inspiration. Here I recommend this Sample from Google LABS:

Address of the project: the project tutorial: https://github.com/googlecodelabs/android-navigation https://codelabs.developers.google.com/codelabs/android-navigation/#0

The advantage of this tutorial Demo is that the official Demo provides a series of detailed tutorials to learn the application scenarios of each class or component step by step, and finally fully use Navigation.

Due to the reason of just released, the Chinese tutorial of Navigation is extremely scarce at present, and the developer may need to provide ladders for searching many materials. But don’t worry, this article will try to be more comprehensive than others.

2. The Sample show

I wrote a Navigation sample, which looks like this:

sample.gif

This is a scene of three simple fragments jumping between each other, with transitions that are smooth and natural. At the end of the demo, you can see that Fragment2 -> Fragment1 is actually triggered by the user hitting the phone’s Back button.

The project structure diagram is as follows, which can help you understand the structure of sample as soon as possible:

I have hosted the source code of this sample on my Github. You can view the source code by clicking on me.

3. Try using Navigation

#### Navigation is currently only supported by AndroidStudio 3.2 and above, if your version is less than 3.2, please click here to download the preview AndroidStudio

First introduce the use of Navigation:

Like it or not, we have to admit that Google has been trying to put Kotlin on top, whether it’s the data presentation at THIS year’s IO conference, the code snippet in the official documentation, or the source code for Google’s latest open source Demo, using Kotlin’s language.

Add the following dependencies to build.gradle in Module:

Dependencies {def nav_version = '1.0.0-alpha01' implementation "android.arch.navigation:navigation-fragment:$nav_version" implementation "android.arch.navigation:navigation-ui:$nav_version"}Copy the code

② Create three fragments:

Class MainPage1Fragment: Fragment() {override fun onCreateView(inflater: override fun onCreateView) LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle?) : View { return inflater.inflate(R.layout.fragment_main_page1, container, false) }}class MainPage2Fragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_main_page2, container, false) }}class MainPage3Fragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_main_page3, container, false) }}Copy the code

Create a navigation view file (nav_graph)

Create a new navigation folder in the RES directory and create a new navigation resource file which I’ll call nav_graph_main.xml:

Open the navigation view file and you can visually edit it on AndroidStudio 3.2, including selecting new fragments or dragging and dropping them to connect them:

4 Edit the navigation view file

We open the Text TAB, go to the XML-edited page, and configure it like this:

<? The 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" app:startDestination="@id/page1Fragment"> <fragment android:id="@+id/page1Fragment" android:name="com.qingmei2.samplejetpack.ui.main.MainPage1Fragment" android:label="fragment_page1" tools:layout="@layout/fragment_main_page1"> <action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> </fragment> <fragment android:id="@+id/page2Fragment" android:name="com.qingmei2.samplejetpack.ui.main.MainPage2Fragment" android:label="fragment_page2" tools:layout="@layout/fragment_main_page2"> <action android:id="@+id/action_page1" app:popUpTo="@id/page1Fragment" /> <action android:id="@+id/action_page3" app:destination="@id/nav_graph_page3" /> </fragment> <navigation android:id="@+id/nav_graph_page3" app:startDestination="@id/page3Fragment"> <fragment android:id="@+id/page3Fragment" android:name="com.qingmei2.samplejetpack.ui.main.MainPage3Fragment" android:label="fragment_page3" tools:layout="@layout/fragment_main_page3" /> </navigation></navigation>Copy the code

Note: Make sure the android:name attribute inside the fragment tag is properly declared.

5. Edit MainActivity

Configuring Navigation in an Activity is very simple. We first edit the Activity’s layout file and add a NavHostFragment to the layout file:

<? The XML version = "1.0" encoding = "utf-8"? ><android.support.constraint.ConstraintLayout 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/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <fragment android:id="@+id/my_nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph_main" /></android.support.constraint.ConstraintLayout>Copy the code

This is a match_parent Fragment with both width and height, which acts as a navigation container.

This is not hard to understand, we need to display a series of fragments in the Activity via Navigation, but we need to tell the Navigation and Activity, Where are these fragments displayed – NavhostFragments came into being, and I described their role as containers for navigation interfaces.

After that, add the following code to your Activity:

class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)    }    override fun onSupportNavigateUp() =            findNavController(this, R.id.my_nav_host_fragment).navigateUp()}Copy the code

A rewrite of the onSupportNavigateUp() method means that the Activity delegates its back button click event, and if it’s not currently at the top of the stack, it clicks the back button to return to the last Fragment.

⑥ Finally, configure jump events corresponding to different fragments

Class MainPage1Fragment: Fragment() {// Hide implementation of onCreateView() method, same as override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) BTN. SetOnClickListener {/ / click jump page2 Navigation. FindNavController (it). Navigate (da ction_page2 R.i)} }}class MainPage2Fragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) BTN. SetOnClickListener {/ / click return page1 Navigation. FindNavController (it). The navigateUp ()} Btn2. SetOnClickListener {/ / click jump page3 Navigation. FindNavController (it). Navigate (da ction_page3 R.i)}}} class MainPage3Fragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, SavedInstanceState) / / click return page2 BTN. SetOnClickListener {Navigation. FindNavController (it). The navigateUp ()}}}Copy the code

As you can see, we don’t control fragments through the native FragmentManager and FragmentTransaction. Rather, it is controlled through the following API:

  • Navigation.findNavController(params).navigateUp()

  • Navigation.findNavController(params).navigate(actionId)

At this point, the basic use of Navigation has been explained. You can run the preview and the sample basically the same effect, if you encounter problems, or have questions, you can click me to view the source code.

Understand the Navigation

I have been thinking about the learning method of summarizing through blog for nearly two years. I keep reflecting on it. An excellent article is not only a complete description, but also an arrangement of ideas and a concise and clean exposition of them.

This is not an easy thing to do, and the first thing to do is to go beyond just using the API — I’ve already implemented Fragment navigation through the code above in my initial learning. However, I don’t recognize any of the activities and fragments in the code above.

I feel very uncomfortable. I don’t know anything about Navigation, let alone Navigation.

The code examples above are meaningless and should be read as a primer. Next we need to understand the responsibilities of each class and understand the thinking of the framework designer.

Let’s start with the question: if we were to implement a Fragment navigation library, what would we implement first?

1.NavGraphFragment: container for navigation interface

The answer is close at hand.

Even if we want to display a Fragment using the native API, we first need to define a container to host it. Where once it might have been a RelativeLayout or FrameLayout, now it’s a NavGraphFragment instead.

This explains why we put a NavGraphFragment in the Activity layout file ahead of time, because the fragments we need to navigate are displayed on the NavGraphFragment.

What does it actually do? Take a look at the NavGraphFragment onCreateView() method:

    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,                             @Nullable Bundle savedInstanceState) {        FrameLayout frameLayout = new FrameLayout(inflater.getContext());        frameLayout.setId(getId());        return frameLayout;    }Copy the code

The NavGraphFragment internally instantiates a FrameLayout that acts as a carrier for the ViewGroup to navigate and display other fragments.

In addition, you should notice that in the Layout file, it declares two other properties:

app:defaultNavHost=”true”app:navGraph=”@navigation/nav_graph_main”

App :defaultNavHost=”true” this property means that your NavGraphFragment will block the system’s Back button click (because the Back button shuts down the Activity instead of switching the Fragment). You must also override the Activity’s onSupportNavigateUp() method, something like this:

override fun onSupportNavigateUp()        = findNavController(R.id.nav_host_fragment).navigateUp()Copy the code

App :navGraph=”@navigation/nav_graph_main”, it points to a navigation_graph XML file, and after that, The NavGraphFragment navigates and displays the Fragment.

In our first step of using Navigation, we need:

Display the declared NavGraphFragment in the Activity layout file and configure the app:defaultNavHost and app:navGraph properties.

2. Nav_graph.xml: Declare navigation structure diagrams

The NavGraphFragment acts as a container for the Activity navigation, and its app:navGraph property points to a Navigation_graph XML file to declare its navigation structure.

Once the NavGraphFragment has retrieved and parsed the XML resource file, the first thing it needs to know is

Where does NavGraphFragment navigate first in the app-like home interface?

<? The 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" app:startDestination="@id/page1Fragment"> <fragment android:id="@+id/page1Fragment" android:name="com.qingmei2.samplejetpack.ui.main.MainPage1Fragment" android:label="fragment_page1" tools:layout="@layout/fragment_main_page1"> <action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> </fragment> // omit... </navigation>Copy the code

Under the root node of navigation, we need to handle the following property:

app:startDestination=”@id/page1Fragment”

Destination is a key word. The app:startDestination property declares that the Destination for this ID will be loaded into the Activity as the default layout. So that’s why our sample, by default, shows MainPage1Fragment.

MainPage1Fragment = MainPage1Fragment = MainPage1Fragment = MainPage1Fragment = MainPage1Fragment = MainPage1Fragment

3. The Action TAB: declares the behavior of the navigation

We declare an Action tag like this, which is a navigational behavior:

<action    android:id="@+id/action_page2"    app:destination="@id/page2Fragment" />Copy the code

App: Destination property declares the destination of this behavior navigation. As you can see, it will redirect to the Fragment with the id of page2Fragment.

Android :id This id is used as the unique identifier for the Action. In the Fragment click event, we point to the corresponding Action by id, like this:

BTN. SetOnClickListener {/ / click jump page2Fragment Navigation. FindNavController (it). Navigate (da ction_page2 R.i)}Copy the code

In addition, Navigation provides an app:popUpTo attribute, which declares that the Navigation behavior will return to the Fragment corresponding to the ID, for example, directly from Page3 to Page1.

In addition, Navigation also provides transition animation support for Navigation behavior, which can be implemented with code like this:

<action        android:id="@+id/confirmationAction"        app:destination="@id/confirmationFragment"        app:enterAnim="@anim/slide_in_right"        app:exitAnim="@anim/slide_out_left"        app:popEnterAnim="@anim/slide_in_left"        app:popExitAnim="@anim/slide_out_right" />Copy the code

For reasons of length, I did not show these ANim XML files in the paper. If necessary, please refer to the Sample code.

Navigation also provides support for passing parameters between destinations and SubNavigation tags, making it easier for developers to reuse fragment tags in XML files — even Deep links. But these extensions are not covered in this article.

Fragment: Declare navigation through code

In fact, in 3 we have already explained the use of navigation code, let’s take Page2 as an example, it contains two buttons, respectively corresponding to return Page1 and enter Page3 two events:

btn.setOnClickListener {      Navigation.findNavController(it).navigateUp()}btn2.setOnClickListener {      Navigation.findNavController(it).navigate(R.id.action_page3)}Copy the code

Navigation. FindNavController (View) returns a NavController, it is the most important core classes, the Navigation architecture we all Navigation behavior by NavController processing, We’ll talk about that later.

We navigate by getting the NavController and then calling the navController.navigate () method.

In most cases, we pass the ActionId to specify the corresponding navigation behavior; The Bundle can also be passed as data; Or pass in a NavOptions configuration for more (such as transitions, which can also dynamically configure code this way).

The navController.navigate () method is more often applied to navigate downward or to specify an upward direction (for example, Page3 returns to Page1 directly, skipping the return to Page2 step); If we handle the back event, we should use navController.navigateUp ().

Congratulations, you can now use Navigation with ease!

Congratulations, you are already familiar with Navigation and have the flexibility to handle page Navigation behavior in your application by skillfully using its exposed apis.

I happily wrote this on my resume:

  • Proficient in using Google’s official component Navigation to achieve Fragment management and master its principle

The interviewer was impressed and asked me to share some personal thoughts about its architectural design.

At this point, we’re kind of API porters, and we’ve understood the responsibilities of each class without fully understanding the thinking of the framework designer.

Thoroughly understand Navigation

Once we are familiar with the Navigation API, we are ready to attack Navigation at source level.

As I said, before you can do this, you need to be proficient in Navigation. The intention of this article is not to do it in one step, but to try to do it step by step.

1. Say NO to source code analysis

Disclaiming – I reject large chunks of source code analysis as I think it seriously degrades the quality and depth of the article.

I spent some time drawing the UML class diagram for Navigation, which I firmly believe will help you and me understand the overall architecture of Navigation more deeply:

UML class diagrams

Let’s change the perspective. We are no longer spectators of the source code, but designers of the architecture.

2. Design NavHostFragment

Navhostfragments should serve two purposes:

  • As a carrier for the Activity navigation interface

  • Manage and control the behavior of navigation

We have already talked about the former function, we create a corresponding FrameLayout for the fragment when it is created as a navigation interface carrier:

    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,                             @Nullable Bundle savedInstanceState) {        FrameLayout frameLayout = new FrameLayout(inflater.getContext());        frameLayout.setId(getId());        return frameLayout;    }Copy the code

We all know that code design should follow the principle of a single responsibility. Therefore, we should hand over the management and control of navigation behavior to another class. This class should only control navigation behavior, hence the name NavController.

The Fragment should hold an instance of the NavController and delegate navigation behavior to it. Here we abstract the owner of the NavController as an interface for future extension.

So we created the NavHost interface and let NavHostFragment implement this interface:

public interface NavHost { NavController getNavController(); }Copy the code

To keep navigation safe, navHostFragments should have only one instance of NavController in their scope.

We stop here, please pay attention to the design of the API, seems to Navigation. FindNavController (View), Passing a reference to any view in the argument seems to get the NavController — how do you guarantee a local singleton of the NavController?

In fact, findNavController(View) is implemented internally by traversing the View tree until it finds the NavController object in the bottom NavHostFragment and returns it:

private static NavController findViewNavController(@NonNull View view) { while (view ! = null) { NavController controller = getViewNavController(view); if (controller ! = null) { return controller; } ViewParent parent = view.getParent(); view = parent instanceof View ? (View) parent : null; } return null; }Copy the code

3. Design NavController

From the designer’s point of view, NavController’s responsibilities are:

  • 1. Parse nav_graph. XML under navigation resource folder

  • 2. Get references to all destinations or classes by parsing the XML

  • 3. Record the sequence of fragments in the current stack

  • 3. Manage and control navigation behaviors

NavController holds a NavInflater and parses XML files through NavInflater.

After that, fetch the Class object of all the destinations (in this case Page1Fragment, Page2Fragment, Page3Fragment) and instantiate the corresponding destinations by reflection. Saved by a queue:

private NavInflater mInflater; //NavInflater private NavGraph mGraph; NavGraph private int mGraphId; // the id corresponding to XML, Private final Deque<NavDestination> mBackStack = new ArrayDeque<>();Copy the code

This doesn’t seem to be a problem, but from a designer’s point of view, Navigation isn’t just for fragments.

Let’s not poke fun at the ambition of the Google engineer, because we are him now. From the perspective of extensiveness, Navigation is a Navigation framework, and in the future it may not only be Fragment Navigation.

We should abstract out the Destination of the navigation. This class is called NavDestination — whether it’s a Fragment or an Activity, as long as we implement this interface, for NavController, They are all destinations.

It is entirely possible that navigation will be different for different navdestinations (such as activities and fragments). How can navigation be handled differently for different Navdestinations?

4. NavDestination and Navigator

The instanceof keyword is used to determine the type of the NavDestination, and then handle it separately. For example:

Else if (destination instanceof Activity) {// Destination instanceof Activity}Copy the code

This is OK, but not elegant. Google does this by abstracting out a class called Navigator:

Public abstract class Navigator<D extends NavDestination> {public abstract class Navigator<D extends NavDestination> Public abstract void navigate(@nonnull D destination, @nullable Bundle args, @nullable NavOptions NavOptions); // instantiate NavDestination (Fragment) public abstract D createDestination(); Public abstract Boolean popBackStack(); }Copy the code

The role of a Navigator is simple:

  • 1. You can instantiate the corresponding NavDestination

  • 2. Can specify navigation

  • 3. Able to navigate backwards

You see, my NavController gets all the Class objects for NavDestinations, but I’m not responsible for how it’s instantiated, or navigated, or backed up — I just hold the reference up and call its interface methods, and I don’t care about its implementation.

Taking FragmentNavigator as an example, let’s look at how it performs its duties:

Public class FragmentNavigator extends the Navigator < FragmentNavigator. Destination > {/ / omit a lot of non-critical code, please give priority to with the actual code! @Override public boolean popBackStack() { return mFragmentManager.popBackStackImmediate(); } @nonnull @override public Destination createDestination() {// Instantiate the Fragment Class<? extends Fragment> clazz = getFragmentClass(); return clazz.newInstance(); } @Override public void navigate(@NonNull Destination destination, @Nullable Bundle args, @nullable NavOptions NavOptions) {// The final Fragment is actually processed using a jump from FragmentTransaction destination.createFragment(args); final FragmentTransaction ft = mFragmentManager.beginTransaction(); ft.replace(mContainerId, frag); ft.commit(); mFragmentManager.executePendingTransactions(); }}Copy the code

Different Navigator corresponding to different NavDestination FragmentNavigator corresponding is FragmentNavigator. The Destination, you can put the fragments of his understanding as a case, Interested friends can do their own research.

5. At this point

So far, the overall architecture design of Navigation has been learned from the perspective of UML class diagram + design analysis.

Of course, there are many other classes for Navigation that I haven’t covered and they can’t stop you or me anymore.

I suggest that after this, readers can try to read the source code, by referring to the UML class diagram above, of course, through the sorting of their own ideas, their own draw a copy, will be more helpful to understand it.

conclusion

Navigation is a good library, which is not reflected in the API, because it and other good tripartite Fragment management libraries achieve fixed goals.

And as technology continues to evolve, it’s only a matter of time before it’s too late. What we can do is use the API, learn its ideas, and make them our own.

Please long press the qr code in the picture below -> identify or scan to follow my official account