Hit the pit! Navigation between Android Jetpack component libraries

An overview of the

Android Jetpack’s Navigation component helps us navigate between pages, from simple button clicks to more complex modes such as title bars and drawer Navigation bars. The navigation component ensures a consistent and predictable user experience by following established principles of navigation.

The Navigation component is intended for applications that have one main Activity and multiple Fragment destinations. The main Activity is associated with the navigation diagram and contains a NavHostFragment responsible for exchanging destinations as needed. It has the following advantages:

  • Have a visual page navigation diagram, easy to see the relationship between the pages;
  • supportNavDirections,ActionAs well asDeepLinkMultiple navigation methods;
  • Simple and convenient use of navigation diagram or code to set the transition animation;
  • Provide type-safe parameter passing between pages;
  • NavigationUIrightToolbar,NavigationView,BottomNavigationView,CollapsingToolbarLayout,DrawerLayoutAnd other controls provide good support.

Note:

  • This article addresses the latest stable release of the Navigation component currently available2.3.5Requires Android Studio 3.3 or higher and relies on Java 8 language features.
  • According to theMigrating to AndroidXDocumentation, yoursgradle.propertiesfileMust also haveandroid.useAndroidX=true.
  • The official toNavigationThe design is not going to be rightFragmentTo save the state, the developer needs to implement it in the ViewModel, so whether you jump to the next page or go back to the previous page,FragmentWill re-walk the life cycle method, to this, this article also gives the solution, referenceProvide safe and reliable Navigation operation.

The basic use

Add the following dependencies to the Project build.gradle file:

Buildscript {repositories {Google ()} dependencies {def nav_version = "2.3.5" classpath "Androidx. Navigation: navigation - safe - the args - gradle - plugin: 2.3.5"}}Copy the code

Add the following dependencies to your app’s build.gradle file:

apply plugin: "Androidx. Navigation. Safeargs dependencies" {/ / Kotlin depend on implementation "Androidx. Navigation: navigation - fragments - KTX: 2.3.5" implementation "androidx. Navigation: navigation - UI - KTX: 2.3.5"}Copy the code

Create a navigation diagram:

  1. In the “Project” window, right-clickresDirectory, and then select in turnNew > Android Resource File. The system displaysNew Resource FileThe dialog box.
  2. Enter a name in the File Name field, such as “nav_graph”.
  3. Select Navigation from the Resource Type drop-down list and click OK.

When the first navigation chart is added, Android Studio creates a Navigation resource directory within the Res directory. This directory contains your navigation graph resource files (for example, nav_graph.xml).

After adding nav_graph.xml, Android Studio will open the file in the Navigation Editor. In the Navigation Editor, you can visually modify the Navigation diagram or modify the underlying XML directly.

The next thing we need to do is hit the + sign to Create a destination, and click Create New Destination to Create the HomeFragment.

A destination is a place where you want to go. This can be a Fragment or an Activity, but the most common one is a Fragment. The purpose of the Navigation component is to make it easy for developers to manage multiple fragments in an Activity.

A Fragment will be generated. The upper right corner defaults to startDestination, which is the Fragment that the NavHostFragment container displays first. If the page cannot be previewed, manually edit the XML file and add a reference to the page layout on the fragment node (for example, tools: Layout =”@layout/ fragment_HOME “).

Click Split in the upper right corner to see the XML file. The
element here is the root element of the navigation diagram. When we add destination and connection actions to the navigation diagram, we can see that the corresponding

and

elements are shown here as child elements. If you have nested views, they will appear as child
elements.

Add a NavHost to the Activity

The Navigation host is one of the core parts of the Navigation component. The navigation host is an empty container into which destinations are swapped as users navigate through our application.

The navigation host must be derived from NavHost. The default NavHost implementation of the Navigation component (NavHostFragment) handles the exchange of Fragment destinations.

The Navigation component is intended for applications that have one main Activity and multiple Fragment destinations. The main Activity is associated with the navigation diagram and contains one responsible for exchanging destinations as neededNavHostFragment. In an application with multiple Activity destinations, each Activity has its own navigation diagram.

Next, we in the XML file to add NavHostFragment MainAcitivty, androidx is recommended here. The fragments. App. FragmentContainerView rather than fragments

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />
Copy the code
  • android:nameAttribute points toNavHostThe name of the class implemented.
  • app:navGraphProperty will beNavHostFragmentAssociated with a navigation diagram. The navigation diagram will be hereNavHostFragmentSpecifies all destinations to which the user can navigate.
  • app:defaultNavHost="true"Property to ensure that yourNavHostFragmentIntercepts the system back button. Note that there can only be one defaultNavHost. If you have multiple hosts in the same layout (for example, a two-pane layout), be sure to specify only one defaultNavHost.

Add a destination to the navigation diagram

We can create a destination from an existing Fragment or Activity. You can also use the Navigation Editor to create new destinations, or to create placeholders to be replaced later with fragments or activities.

Connecting destination

You can use the Navigation Editor to connect the two destinations as follows:

  • In the Design TAB, hover the mouse to the right of the destination, which is the destination you want the user to navigate out of. A circle appears above the right of the destination;
  • Click the destination you want the user to navigate to, drag the cursor over the destination, and then release. The lines generated between the two destinations represent the operations.

Click Code in the upper right corner to switch to the XML view and you can see:

<? 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/nav_graph" app:startDestination="@id/homeFragment"> <fragment android:id="@+id/homeFragment" android:name="com.soushin.tinmvvm.mvvm.ui.fragment.HomeFragment" android:label="HomeFragment" tools:layout="@layout/fragment_home"> <action android:id="@+id/action_homeFragment_to_categoryFragment" app:destination="@id/categoryFragment" /> </fragment> <fragment android:id="@+id/categoryFragment" android:name="com.soushin.tinmvvm.mvvm.ui.fragment.CategoryFragment" android:label="CategoryFragment" tools:layout="@layout/fragment_category"/> </navigation>Copy the code

In the navigation diagram, connections are represented by

elements. The connection should at least contain the ID of

and the ID of the destination to which the user should go (the ID to which destination points).

Navigate to your destination

Navigating to a destination is done using NavController, an object that manages application navigation in NavHost. Each NavHost has its own NavController. You can get the NavController using one of the following methods:

Kotlin:

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

Java:

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, @IdRes int viewId)
  • Navigation.findNavController(View)

Pass data between destinations

When Safe Args is enabled, type-safe classes and methods are generated for the start and end points of each action. The specific operations are as follows:

  1. In the Navigation Editor, click the end of the receive parameter;
  2. In the Attributes panel, click Add (+);
  3. In the Add Argument Link window that is displayed, enter the parameter name, parameter type, whether the parameter can be null, and the default value (if required).
  4. Click Add. Notice that this parameter is now displayed in the Arguments list on the Attributes panel;
  5. You can also see that this parameter has been added to the XML. Click the Code TAB to switch to the XML view, and you’ll see that your parameter has been added to the destination that received it. The following is an example:
    <fragment
        android:id="@+id/categoryFragment"
        android:name="com.soushin.tinmvvm.mvvm.ui.fragment.CategoryFragment"
        android:label="CategoryFragment"
        tools:layout="@layout/fragment_category">
        <argument
            android:name="pageType"
            app:argType="integer" />
    </fragment>
Copy the code

In addition to statically passing values in XML files, you can also set them dynamically. We in the beginning by NavController navigate () navigation, and use HomeFragmentDirections. ActionHomeFragmentToCategoryFragment (99) carried by the cords, We then get the parameter passed in at the end of the line with CategoryFragmentArgs:

/ / start setting parameter type safety Navigation. FindNavController (v) navigate (HomeFragmentDirections. ActionHomeFragmentToCategoryFragment (99)) Val args: CategoryFragmentArgs by navArgs<CategoryFragmentArgs>() println(args.pageType)Copy the code

Stomp pit: The button is called repeatedly when we quickly click itnavigate()The following exceptions are generated:

java.lang.IllegalArgumentException: Navigation action/destination com.soushin.tinmvvm:id/action_homeFragment_to_categoryFragment cannot be found from the current destination Destination(com.soushin.tinmvvm:id/categoryFragment) label=CategoryFragment class=com.soushin.tinmvvm.mvvm.ui.fragment.CategoryFragment
Copy the code

This is due to the fact that the current page has changed since the first navigate() call, and that our action has a start and end point. Therefore, if the start point of the second call is no longer HomeFragment, the exception will be reported. This is very obvious when adding a transition animation (transitions have a duration) to a page jump, just check if currentDestination is the destination for the current page before jumping. The following is an example:

if (Navigation.findNavController(v).currentDestination? .id ! = R.id.homeFragment) returnCopy the code

Use NavigationUI to update the page

The Navigation component contains the NavigationUI class. This class contains a variety of static methods to help you manage navigation using the Toolbar, NavigationView, BottomNavigationView, CollapsingToolbarLayout, and DrawerLayout.

At the top of the title bar (the Toolbar/ActionBar/CollapsingToolbarLayout)

NavigationUI has built-in methods that automatically update the top title bar while the user is browsing the app. For example, NavigationUI can use the destination TAB in the navigation diagram to update the title of the top title bar in a timely manner.

<navigation>
    <fragment ...
              android:label="Page title">
      ...
    </fragment>
</navigation>
Copy the code

Another syntax sugar is that if we use NavigationUI as described below with the top title bar implementation, we can use the {argName} format in the tag to automatically fill in the tag attached to the destination based on the parameters provided to the destination.

NavigationUI supports the following top title bar types:

  • Toolbar
  • CollapsingToolbarLayout
  • ActionBar
AppBarConfiguration

NavigationUI uses the AppBarConfiguration object to manage the behavior of the navigation button in the upper-left corner of the application display area. The behavior of the navigation button changes depending on whether the current page is at a top-level destination.

A top-level destination is the root or highest level destination in a hierarchy of destinations. Top-level destinations do not display a “Back” button in the top title bar because there are no higher-level destinations. By default, the application destination is the only top-level destination.

When the user is at the top level destination, the navigation button will become a drawer-like navigation bar icon if the destination is using DrawerLayout. If the destination does not use DrawerLayout, the navigation buttons are hidden. When the user is at any other destination, the navigation button appears as the back button. To use the start destination as the only top-level destination when configuring the navigation buttons, create an AppBarConfiguration object and pass in the corresponding navigation diagram, as shown below:

val appBarConfiguration = AppBarConfiguration(navController.graph)
Copy the code

In more cases, we might want to define multiple top-level destinations instead of using the default destination. A common use case for this is BottomNavigationView, where sibling screens may not be hierarchical with each other and may each have a related set of destinations. In such a case, you can instead pass a set of destination ids to the constructor, as follows:

val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.profile))
Copy the code
To create the Toolbar

To create a toolbar using NavigationUI, define the toolbar in the main activity as follows:

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar" />
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
</LinearLayout>
Copy the code

Next, call setupWithNavController() from the onCreate() method of the main activity, as shown below:

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navController = findNavController(R.id.nav_host_fragment)
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    findViewById<Toolbar>(R.id.toolbar)
        .setupWithNavController(navController, appBarConfiguration)
}
Copy the code

Note: When using the Toolbar, the Navigation component automatically handles Navigation button clicks, so we don’t need to rewrite onSupportNavigateUp().

To configure the navigation button to appear as a return button at all destinations, pass a set of blank destination ids for the top-level destinations when building AppBarConfiguration. For example, it might be useful if we have a second activity that should display a back button in the Toolbar for all destinations. This allows the user to navigate back to the parent activity when there are no other destinations on the return stack. You can use setFallbackOnNavigateUpListener (control) in navigateUp () without performing any operation of the fallback behavior, as shown in the example below:

override fun onCreate(savedInstanceState: Bundle?) {... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController val appBarConfiguration = AppBarConfiguration( topLevelDestinationIds = setOf(), fallbackOnNavigateUpListener = ::onSupportNavigateUp ) findViewById<Toolbar>(R.id.toolbar) .setupWithNavController(navController, appBarConfiguration) }Copy the code
Using CollapsingToolbarLayout

To add the CollapsingToolbarLayout in the toolbar, define the toolbar and surrounding layout in the activity as follows:

<LinearLayout> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="@dimen/tall_toolbar_height"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:contentScrim="? attr/colorPrimary" app:expandedTitleGravity="top" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="? attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" ... / >... </LinearLayout>Copy the code

Next, setupWithNavController() is called through the onCreate method of the main activity, as shown below:

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout)
    val toolbar = findViewById<Toolbar>(R.id.toolbar)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    layout.setupWithNavController(toolbar, navController, appBarConfiguration)
}
Copy the code
Use ActionBar

Default action bar if you need to add navigation support, please through the main activity of the onCreate () method call setupActionBarWithNavController (), as shown below. Note that you need to declare AppBarConfiguration outside of onCreate(), because you also use this method when replacing onSupportNavigateUp() :

private lateinit var appBarConfiguration: AppBarConfiguration ... override fun onCreate(savedInstanceState: Bundle?) {... val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration) }Copy the code

Next, replace onSupportNavigateUp() to handle navigation up:

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}
Copy the code
Different styles of title bar are supported

Adding a top title bar to an activity works well if the title bar layout is similar for each destination in the application. However, if the top title bar varies greatly from destination to destination, consider removing the top title bar from the activity and defining it in each destination fragment instead.

For example, one destination might use the standard Toolbar, while another destination uses AppBarLayout to create a more complex title bar with tabs.

To implement this example in the Destination Fragment using NavigationUI, first define a title bar in each fragment layout, starting with the destination fragment in the standard toolbar:

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        ... />
    ...
</LinearLayout>
Copy the code

Next, define a destination fragment that uses an application bar with tabs:

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        ... />

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            ... />

        <com.google.android.material.tabs.TabLayout
            ... />

    </com.google.android.material.appbar.AppBarLayout>
    ...
</LinearLayout>
Copy the code

The navigation configuration logic for these two fragments is the same, but you should call setupWithNavController() in the onViewCreated() method of each fragment instead of initializing them through the activity:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController()
    val appBarConfiguration = AppBarConfiguration(navController.graph)

    view.findViewById<Toolbar>(R.id.toolbar)
            .setupWithNavController(navController, appBarConfiguration)
}
Copy the code

Note: If you place the top application bar in the destination fragment layout while setting the fragment transition, the title bar and the layout will transition together during the fragment switchover.

Using DrawerLayout

DrawerLayout is the interface that displays the main navigation menus of your application. The DrawerLayout is displayed when the user touches the drawer navigation icon in the app bar or when the user swipes a finger from the left edge of the screen.

Drawer – style navigation ICONS will be displayed in useDrawerLayoutAll of theTop-level destinationOn.

To add a drawer navigation bar, declare DrawerLayout as the root view. Within the DrawerLayout, add the layout to the main interface content and other views that contain the contents of the drawer-style navigation bar.

For example, the following layout uses a DrawerLayout with two sub-views: a NavHostFragment for the main content and a NavigationView for the contents of the drawer navigation bar.

<? The XML version = "1.0" encoding = "utf-8"? > <! -- Use DrawerLayout as root container for activity --> <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <! -- Layout to contain contents of main body of screen (drawer will slide over this) --> <androidx.fragment.app.FragmentContainerView android:name="androidx.navigation.fragment.NavHostFragment" android:id="@+id/nav_host_fragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" /> <! -- Container for contents of drawer - use NavigationView to make configuration easier --> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" /> </androidx.drawerlayout.widget.DrawerLayout>Copy the code

Next, pass the DrawerLayout to AppBarConfiguration to connect it to the navigation diagram, as shown in the following example:

Note: When using NavigationUI, the top title bar helper automatically switches between the drawer navigation bar icon and the return icon as the current destination changes. No ActionBarDrawerToggle.

Next, in our main Activity class, we call setupWithNavController() through the main Activity’s onCreate() method, as shown below:

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<NavigationView>(R.id.nav_view)
        .setupWithNavController(navController)
}
Copy the code

Using BottomBarNavigation

NavigationUI can also handle bottom navigation. When the user selects a menu item, the NavController calls onNavDestinationSelected() and automatically updates the selected item in the bottom navigation bar.

To create a bottom navigation bar in an application, define the bottom navigation bar in the main activity as follows:

<LinearLayout>
    ...
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav"
        app:menu="@menu/menu_bottom_nav" />
</LinearLayout>
Copy the code

Next, in your main Activity class, call setupWithNavController() through the onCreate() method of the main activity, as shown below:

override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)

    ...

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    findViewById<BottomNavigationView>(R.id.bottom_nav)
        .setupWithNavController(navController)
}
Copy the code

Related information:

Android Jetpack Navigation

FragmentPagerAdapter, FragmentStatePagerAdapter and FragmentStateAdapter difference you know

Android Jetpack Best Practices! Combined with MVVM rapid development, help developers understand Jetpack!