preface

Navigation is one of the Android Jetpack components that makes single-activity apps the preferred architecture. In-app Fragment page jumps are handled by Navigation, eliminating the need to deal with the complexity of FragmentTransaction and associated transitions.

The specific use

Add dependencies to gradle.build of app:

def nav_version = "2.1.0."implementation "androidx.navigation:navigdef nav_version = "2.1.0" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"Copy the code

First we define three fragments: Fragment1, Fragment2, and Fragment3. Implementation logic: Click Fragment1 to jump to Fragment2, click Fragment2 to jump to Fragment3, click Fragment3 to Jump to Fragment1 and click the back button to return to Fragment1.

Navigation: The root of the navigation view XML. Where the jump logic of the fragment is defined.

The first step is to create a navigation folder in the RES resource directory and right-click to create a navigation resource file named nav_graph_main.xml.

The bottom left foot of the file is split into two tabs: Design and Text. The Design view is visual and you can select the fragment directly. The Text view is our hand-written configuration.

Let’s take a look at the nav_graph_main.xml file defined:

<? 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/fragment1">

    <fragment
        android:id="@+id/fragment1"
        android:name="com.jetpack.jetpackdemo.navigation.fragment.Fragment1"
        android:label="Fragment1"
        tools:layout="@layout/fragment1_layout">

        <action
            android:id="@+id/fragment1_action"
            app:destination="@+id/fragment2" />

    </fragment>

    <fragment
        android:id="@+id/fragment2"
        android:name="com.jetpack.jetpackdemo.navigation.fragment.Fragment2"
        android:label="Fragment2"
        tools:layout="@layout/fragment2_layout">

        <action
            android:id="@+id/fragment2_action"
            app:destination="@+id/fragment3" />

    </fragment>

    <fragment
        android:id="@+id/fragment3"
        android:name="com.jetpack.jetpackdemo.navigation.fragment.Fragment3"
        android:label="Fragment3"
        tools:layout="@layout/fragment3_layout">

        <action
            android:id="@+id/fragment3_action"
            app:popUpTo="@id/fragment1" />

    </fragment>

</navigation>Copy the code

Navigation has a startDestination field in the root node, which indicates which page is displayed by default. Use the Fragment tag to define the relevant page to route. Id Uniquely identifies the fragment. Name indicates the package name and must be correct. Layout is a fragment layout file. After configuration, you can view it in the Design view.

Fragment has child actions configured. Action indicates the specific behavior to be routed. The ID is also the unique identifier. Destination indicates the destination, that is, to route to a specific page. PopUpTo indicates that a page is displayed. Action has other properties such as configuration animation, see Demo for more details.

NavHostFragment is a display container for navigation views.

<fragment
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    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_main" />Copy the code

Name is a fixed notation and must be specified as

androidx.navigation.fragment.NavHostFragmentCopy the code

The defaultNavHost field indicates whether to intercept the return key operation.

If true, override the onSupportNavigateUp method in the required Activity.

By default, the return key does not roll back the fragment page.

override fun onSupportNavigateUp(): Boolean {
    return findNavController(R.id.nav_host_fragment).navigateUp()
}Copy the code

The navGraph field is the navigation view configured for us.

NavController

FindNavController is used to obtain the NavController, and navigate between pages through the Controller’s navigate or navigateUp.

The logic of clicking buttons on three pages is to challenge the corresponding page:

mBtn.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.fragment1_action)

}Copy the code

Tells Navigation the logic of the jump by specifying the id of the action. The same goes for other pages.

End result:


Let’s summarize the relationship between Navigation, NavHostFragment and NavController.

Navigation means planning a lot of routes that need to be displayed in NavHostFragment. After showing so many routes, the NavController decides which route to take, like a steering wheel.

Passing parameters

In the previous section we explained navigation, which also has a child tag: argument. Is used to define parameters. For example, we add the argument tag to the fragment2 tag like this:

<argument
    android:name="name"
    android:defaultValue="Navigation navigation"
    app:argType="string"
    app:nullable="false" />Copy the code

You can then carry parameters when Fragment1 jumps to Fragment2. Name indicates the parameter name. DefaultValue is the defaultValue. ArgType is the type of the argument. Nullable: indicates whether it can be null.

There are two ways to pass parameters between fragments:

  • The traditional Bundle approach
  • SafeArgs via Google

The traditional Bundle approach

Set and get parameters through the Bundle.

Set this in fragment1:

Mbtn.setonclicklistener {// If you want to use the default argument in XML, just pass in new Bundle(). Val args = Bundle() args.putString()"name"."Passing parameters through the bundle")

    Navigation.findNavController(it).navigate(R.id.fragment1_action, args)

}Copy the code

Get the parameter in fragment2:

val args = arguments val name = args? .getString("name")

mTvName.text = nameCopy the code

This allows the parameters to be passed. Is there anything wrong with this approach?

Parameter name “name” is manually filled in three places. This can easily lead to spelling mistakes and missing corrections. Very unfriendly. So Google gave us a plugin: safeArgs. Now let’s look at the specific use.

safeArgs

First you need to configure it by adding the classpath configuration to your project’s build.gradle:

dependencies {
    classpath 'android. Arch. Navigation: navigation - safe - the args - gradle - plugin: 1.0.0'
}Copy the code

Add the Apply Plugin to build.gradle of app.

apply plugin: 'androidx.navigation.safeargs'Copy the code

After the project is rebuilt, it will know that the file with the suffix Directions is generated for the fragment. And automatically generates a file with the suffix Args for the fragment with the argument tag in navigation.

Set the parameters from the file with the suffix Directions. Obtain parameters from files with the suffix Args.

Set in fragment1:

mBtn.setOnClickListener {
    
    val args = Fragment1Directions.fragment1Action().setName("Parameter passing via safeArgs")
    Navigation.findNavController(it).navigate(R.id.fragment1_action, args.arguments)

}Copy the code

Obtain from fragment2:

val name = Fragment2Args.fromBundle(arguments!!) .name mTvName.text = nameCopy the code

This completes the passing of parameters between fragments. The logic of manually setting parameters is completely avoided. Operate on parameters directly through setters and getters.

conclusion

Generally speaking, Navigation is not complicated to use and it makes it possible to use a single Activity architecture without worrying about the specific fragment jump logic. But there are also problems, as we know from source code analysis

In the onCreateView of NavHostFragment, FrameLayout is created, so the real container is FrameLayout. Instead of show and hide, the replace API is used internally to create FragmentNavigator. This causes the fragment to be reexecuted every time it lives. So it should work better with the ViewModel.