This is the second MAD Skills series on Navigation. If you want to review past releases, check out the link below:

  • Overview of navigation components
  • Navigate to the dialog box
  • Use SafeArgs when navigating your application
  • Use deep link navigation
  • Create your first app bundle | MAD Skills

Today is the first in a series of articles. In this article, we’ll show you another use case, which is how UI components like the Action Bar, bottom TAB Bar, or drawer navigation Bar can be navigated in an application. If you’d rather watch the video than read the article, check out the video content.

An overview of the

In the previous navigation series, Chet developed an application for tracking doughnuts. You know what a perfect partner for a donut is? (Another donut?) Coffee, of course! So I’m going to add a coffee tracking feature. I needed to add some pages to the app, so it was necessary to use a drawer navigation bar or bottom TAB bar to help the user navigate. But how do we use these UI components to integrate navigation? Manually triggering the navigation action by clicking on the listener?

Don’t need to! No listeners are required. The NavigationUI class matches the target page ID with the menu ID to achieve navigation between different pages. Let’s take a closer look at its inner workings.

Add a coffee tracker

△ Engineering structure

First I copied the donut-related class files into the new package and renamed them. This may not be the best practice for a real application, but it’s a quick way to add coffee tracking to an existing application. If you want to synchronize with the content of the article, you can get the code here, which contains all the changes for the Donut Tracker application, and learn NavigationUI from it.

Based on the above changes, I updated the navigation diagram and added destination pages and operations related to coffeeFragment to coffeeDialogFragment and selectionFragment to donutFragment. And then I’m going to use the ids of these destination pages;)

△ Navigation diagram with new destination page

With the navigation diagram updated, we can start binding elements together and implement navigation to the SelectionFragment.

In the options menu

The options menu for the application is not currently available. To enable it, in the onOptionsItemSelected() function, call the onNavDestinationSelected() function for the selected menu item and pass in the navController. As long as the id of the destination page matches the ID of the MenuItem, the function navigates to the destination page bound to the MenuItem.

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return item.onNavDestinationSelected(
       findNavController(R.id.nav_host_fragment)
   ) || super.onOptionsItemSelected(item)
}
Copy the code

Now that the navigation controller can “dominate” the MenuItem, I match the id of the MenuItem to the id of the destination page I created earlier. In this way, the navigation component can associate the MenuItem with the destination page.

<menu 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"
   tools:context="com.android.samples.donuttracker.MainActivity">
   <item
       android:id="@+id/selectionFragment"
       android:orderInCategory="100"
       android:title="@string/action_settings"
       app:showAsAction="never" />
</menu>
Copy the code

Toolbar

Now the application can navigate to the selectionFragment, but the title remains the same. When we are in the selectionFragment, we want the title to be updated and the back button to be displayed.

First I need to add an AppBarConfiguration object, which the NavigationUI will use to manage the behavior of the navigation buttons in the upper-left corner of the application.

appBarConfiguration = AppBarConfiguration(navController.graph)
Copy the code

The button changes its behavior according to the hierarchy of your destination page. For example, when you are on the top-level destination page, the back button is not displayed because there are no higher-level pages.

By default, the initial page you apply is the only topmost destination page, but you can define multiple topmost destination pages. For example, in our application, I can define the destination pages of both donutList and coffeeList as the topmost destination pages.

Next, in the MainActivity class, get the navController and Toolbar instances and verify that setSupportActionBar() was called. Here I also updated the reference to the toolbar that was passed to the function.

val navHostFragment = supportFragmentManager.findFragmentById(
   R.id.nav_host_fragment
) as NavHostFragment
navController = navHostFragment.navController
val toolbar = binding.toolbar
Copy the code

Want to be in the default Action Bar (Action Bar) to add the navigation function, here I am using the setupActionBarWithNavController () function. This function takes two arguments: navController and appBarConfiguration.

setSupportActionBar(toolbar)
setupActionBarWithNavController(navController, appBarConfiguration)
Copy the code

Next, I override the onSupportNavigationUp() function based on the current destination page, NavigateUp () is then called on nav_host_fragment and appBarConfiguration is passed in to support rollback navigation or display menu ICONS.

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

Now I can navigate to the selectionFragment, and you can see that the title has been updated and the back button is displayed, allowing the user to return to the previous page.

The title has been updated and the back button is also displayed

Bottom TAB bar

So far so good, but the application can’t navigate to the coffeeList Fragment yet. We’re going to solve that problem.

Let’s start by adding a bottom TAB bar. Start by adding the bottom_nav_menu. XML file and declaring two menu elements. NavigationUI relies on the ID of the MenuItem to match the id of the destination page in the navigation diagram. I also set ICONS and titles for each destination page.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@id/donutList"
       android:icon="@drawable/donut_with_sprinkles"
       android:title="@string/donut_name" />
   <item
       android:id="@id/coffeeList"
       android:icon="@drawable/coffee_cup"
       android:title="@string/coffee_name" />
</menu>
Copy the code

Now that the MenuItem is ready, I’ve added a BottomNavigationView to the layout of my mainActivity and set bottom_nav_menu to the menu property of the BottomNavigationView.

<com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />
Copy the code

To make the bottom TAB bar work, call the setupWithNavController() function to pass the navController to the BottomNavigationView.

private fun setupBottomNavMenu(navController: NavController) {
   valbottomNav = findViewById<BottomNavigationView>( R.id.bottom_nav_view ) bottomNav? .setupWithNavController(navController) }Copy the code

Note that I am not calling any navigation operations from the navigation diagram. In fact, there isn’t even a path to the coffeeList Fragment in the navigation diagram. As we did with the ActionBar, the BottomNavigationView automatically responds to the navigation action by matching the ID of the MenuItem with the ID of the destination page.

Drawer type navigation bar

While it looks good, the bottom TAB bar may not provide the best user experience if your device has a large screen size. To fix this, I’ll use another layout file with the W960DP qualifier to indicate that it works on devices with larger, wider screens.

This layout file is similar to the default Activity_main layout, which already includes the Toolbar and FragmentContainerView. I need to add the NavigationView and set nav_DRAwer_MENU to the MENU property of the NavigationView. Next, I’ll add a separator between the NavigationView and the FragmentContainerView.

<RelativeLayout
   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="com.android.samples.donuttracker.MainActivity">
   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:menu="@menu/nav_drawer_menu" />
   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="? android:attr/listDivider" />
   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:background="@color/colorPrimary"
       android:layout_toEndOf="@id/nav_view"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       app:defaultNavHost="true"
       android:layout_toEndOf="@id/nav_view"
       app:navGraph="@navigation/nav_graph" />
</RelativeLayout>
Copy the code

In this way, on a wide screen device, the NavigationView will be displayed on the screen instead of the BottomNavigationView. Now that the layout file is in place, I create another nav_drawer_menu. XML and add the donutList and coffeeList as the main groups for the destination pages. For the MenuItem, I added the selectionFragment as its destination page.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <group android:id="@+id/primary">
       <item
           android:id="@id/donutList"
           android:icon="@drawable/donut_with_sprinkles"
           android:title="@string/donut_name" />
       <item
           android:id="@id/coffeeList"
           android:icon="@drawable/coffee_cup"
           android:title="@string/coffee_name" />
   </group>
   <item
       android:id="@+id/selectionFragment"
       android:title="@string/action_settings" />
</menu>
Copy the code

Now that we have all the layouts in place, let’s go back to MainActivity and set up the drawer navigation bar to work with the NavigationController. Similar to what we did with the BottomNavigationView, create a new method and pass the navController into the NavigationView by calling the setupWithNavController() function. To keep the code clean and clear between the elements, we’ll do it in a new method and call it in onCreate().

private fun setupNavigationMenu(navController: NavController){
   valsideNavView = findViewById<NavigationView>(R.id.nav_view) sideNavView? .setupWithNavController(navController) }Copy the code

Now when I run the app on a device with a wider screen, I can see that the drawer navigation has the MenuItem set and that the MenuItem matches the id of the destination page in the navigation diagram.

Run Donut Tracker on a device with a wider screen

Notice that the back button automatically appears in the top left corner when I switch pages. If you want to do this, you can also modify AppBarConfiguration to add CoffeeList as the top-level destination page.

summary

That’s all for this time. The Donut Tracker app doesn’t require a bottom TAB bar or a draw-out navigation bar, but with the addition of new features and destination pages, NavigationUI is a great way to handle navigation in the app.

We don’t need to do anything else, just add the UI component and match the ID of the MenuItem with the id of the destination page. You can look at the complete code and see how it changes by comparing main to the starter branch.