In daily development, especially in large projects, more and more activities are used to nest multiple fragments in the UI mode of development. However, the Fragment life cycle, hidden display, animation control and switching and transferring values are quite troublesome. The usual way to manage this is through the FragmentManager, which has redundant code and is difficult to maintain.

Jetpack provides Navigation components to help developers develop and maintain fragments more efficiently and easily. Navigation components can be programmed visually, like XML layouts, and are easier to develop. They provide properties for jumping between fragments, animation and parameter transfer between fragments, and deeplink support.

Main components of Navigation

  • Navigation Graph: a new XML file that internally defines fragments and their relationships;
  • NavHostFragment: a special type of Fragment. The name of the Fragment can be seen as a container of fragments. Navhostfragments defined in the Navigation Graph are displayed through navhostFragments.
  • NavController: A Java class that performs operations such as jumping between fragments.

use

Create Navigation Graph

Navigation Graph is an XML file and also a res resource, so when creating a Navigation Graph, you need to operate in the RES directory:

Click New in the res directory, select Android Resource, select Navigation for Resource Type, name the file, and click OK to create a Navigation Graph file. After creating the file, open it as shown below:

2. Create NavHostFragment

NavHostFragment is a class provided by Jetpack. We can use it directly in the XML layout of the Activity. The code is as follows:

<? The 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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".NavigationActivity"> <fragment android:id="@+id/nav_host" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/navigation_graph" /> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code

To note here is that we need to add the Activity on the XML layout fragments, under this element to add name attribute, and specify is the android: name = “androidx. Navigation. Fragments. NavHostFragment”, If defaultNavHost is set to true, the Fragment will default as a NavHostFragment and the navGraph property will be used to execute the corresponding Navigation Graph file.

This will open the Navigation Graph file we created earlier and you’ll see that the HOST option on the Destinations panel already sets what we declared in the Activity XML:

3. Create destination

In the Navigation Graph, click on the + sign as shown, select Create New Destination, select a Blank Fragment and create it, as shown:

Destination refers to the target Fragment, that is, the next Fragment. Once created, you can see the defined Fragment in the GRAPH option in the Destination panel:

NavigationOneFragment has a “start” at the end of the Fragment, indicating the initial Fragment, which is the first Fragment shown. Now look at the Navigation Graph XML file:

<? 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" android:id="@+id/navigation_graph" app:startDestination="@id/navigationOneFragment"> <fragment android:id="@+id/navigationOneFragment" android:name="com.jia.demo.NavigationOneFragment" android:label="fragment_navigation_one" tools:layout="@layout/fragment_navigation_one" /> </navigation>Copy the code

The outermost navigation node has a property, startDestination, that specifies the Fragment we created.

4. Switch fragments

Then create a NavigationTwoFragment in the same way. Select NavigationOneFragment and drag it to NavigationTwoFragment.

Look at the Graph file again:

<? 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" android:id="@+id/navigation_graph" app:startDestination="@id/navigationOneFragment"> <fragment android:id="@+id/navigationOneFragment" android:name="com.jia.demo.NavigationOneFragment" android:label="fragment_navigation_one" tools:layout="@layout/fragment_navigation_one" > <action android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment" app:destination="@id/navigationTwoFragment" /> </fragment> <fragment android:id="@+id/navigationTwoFragment" android:name="com.jia.demo.NavigationTwoFragment" android:label="fragment_navigation_two" tools:layout="@layout/fragment_navigation_two" /> </navigation>Copy the code

NavigationOneFragment contains an action child that points to the navigationTwoFragment. The next page that represents NavigationOneFragment is NavigationTwoFragment.

Now it’s time to use the NavController mentioned earlier.

To improve the NavigationOneFragment layout, add a Button and set the click event:

private const val ARG_PARAM1 = "param1" private const val ARG_PARAM2 = "param2" class NavigationOneFragment : Fragment() { private var param1: String? = null private var param2: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments? .let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { return inflater.inflate(R.layout.fragment_navigation_one, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button_jump.setOnClickListener { Navigation.findNavController(it) .navigate(R.id.action_navigationOneFragment_to_navigationTwoFragment) } // button_jump.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_navigationOneFragment_to_navigationT woFragment)) } companion object { @JvmStatic fun newInstance(param1: String, param2: String) = NavigationOneFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } } }Copy the code

In addition to using Navigation. FindNavController (view). Navigate (), you can also use: Navigation. CreateNavigateOnClickListener directly create a an OnClickListener (). The id passed in is the ID of the action node in the Graph file.

This allows you to create a Fragment and implement a Fragment switch.

5. Set animation for Fragment toggle

To add an animation to the Fragment switch, you need to define the animation in the Anim folder.

In the Destinations panel of the Graph, select the jump arrow and the Attributes panel on the right displays the Animations area, as shown:

For convenience, I’m going to use the default animation here, and look at the graph file:

<? 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" android:id="@+id/navigation_graph" app:startDestination="@id/navigationOneFragment"> <fragment android:id="@+id/navigationOneFragment" android:name="com.jia.demo.NavigationOneFragment" android:label="fragment_navigation_one" tools:layout="@layout/fragment_navigation_one" > <action android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment" app:destination="@id/navigationTwoFragment" app:enterAnim="@anim/nav_default_enter_anim" app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> </fragment> <fragment android:id="@+id/navigationTwoFragment" android:name="com.jia.demo.NavigationTwoFragment" android:label="fragment_navigation_two" tools:layout="@layout/fragment_navigation_two" /> </navigation>Copy the code

There are four more properties in the Action node, one for each animation, which adds animation to the Fragment switch.

In addition to visualizing the setup animation in the Graph file, you can also set it in code:

       button_jump.setOnClickListener {
            val options = NavOptions.Builder()
                .setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim)
                .build()
            Navigation.findNavController(it)
                .navigate(R.id.action_navigationOneFragment_to_navigationTwoFragment, null, options)
        }
Copy the code

Create the NavOptions object and set each animation, passing it in the Navigate method, whose second parameter is the Buddle object, as described below.

Pass parameters in Fragment

1. Common parameter transmission

Our longest use is to pass data between fragments via the Bundle. The NavController provides overloaded navigate methods to pass in the Bundle object:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        button_jump.setOnClickListener {
            val options = NavOptions.Builder()
                .setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim)
                .build()

            var bundle = Bundle()
            bundle.putString("key", "value")

            Navigation.findNavController(it).navigate(
                R.id.action_navigationOneFragment_to_navigationTwoFragment,
                bundle,
                options
            )
        }
    }
Copy the code

Receive in target Fragment:

private const val KEY = "key" class NavigationTwoFragment : Fragment() { private var value: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments? .let { value = it.getString(KEY) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { return inflater.inflate(R.layout.fragment_navigation_two, container, false) } }Copy the code

2. Pass parameters using safe args

To use safe args, you first need to add dependencies and plug-ins in the build.gradle file of the project:

Dependencies {classpath "androidx. Navigation: navigation - safe - the args - gradle - plugin: 2.3.0"}Copy the code

In the build.gradle file of the Module:

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

Add the argument node to the fragment node of the Graph file at the same level as the action:

<? 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" android:id="@+id/navigation_graph" app:startDestination="@id/navigationOneFragment"> <fragment android:id="@+id/navigationOneFragment" android:name="com.jia.demo.NavigationOneFragment" android:label="fragment_navigation_one" tools:layout="@layout/fragment_navigation_one"> <action android:id="@+id/action_navigationOneFragment_to_navigationTwoFragment" app:destination="@id/navigationTwoFragment" app:enterAnim="@anim/nav_default_enter_anim" app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> <argument android:name="params" android:defaultValue="default_value" app:argType="string" /> </fragment> <fragment android:id="@+id/navigationTwoFragment" android:name="com.jia.demo.NavigationTwoFragment" android:label="fragment_navigation_two" tools:layout="@layout/fragment_navigation_two" /> </navigation>Copy the code

After you add the Arguments node, Java code is automatically generated:

Pass parameters:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        button_jump.setOnClickListener {
            val options = NavOptions.Builder()
                .setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim)
                .build()

            var bundle = NavigationOneFragmentArgs.Builder()
                .setParams("one param")
                .build()
                .toBundle()

            Navigation.findNavController(it).navigate(
                R.id.action_navigationOneFragment_to_navigationTwoFragment,
                bundle,
                options
            )
        }
    }
Copy the code

From the automatically generated NavArgs class, Bundle objects are created using Builder mode, and arguments can be passed during Fragment switching in the same manner described earlier. The same goes for receiving:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var params = arguments? .let { NavigationOneFragmentArgs.fromBundle(it).params} textView.text = params }Copy the code

Safe Args, as the name suggests, is a safe parameter that is easier and safer to use.

Jetpack provides developers with a Navigation component that allows them to easily, quickly and visually switch between fragments and animate and transfer values.