Photo by Bryan Minear on Unsplash

In the previous post, we introduced the Jetpack Navigation Components framework. Today we are going to see how we can make the most of it! What are the steps we have to follow in order to implement navigation with this new library? Let’s review them together in this article.

Project configuration

To begin with, we add the required dependencies in the gradle file:

implementation "androidx.navigation:navigation-fragment-ktx:$navVersion" 
   
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"

The module “navigation-fragment” contains the core components, whereas “navigation-ui” contains extensions functions used to hook UI components with navigation actions.

All libraries and plugins are available for both Java and Kotlin ( with “-ktx” suffix). You can check them here.

Adding UI controllers

As stated in the principles of navigation recommended by Google, activities basically contain global navigation mechanisms (like the navigation drawer) and fragment containers are used as placeholders for current screen content.

According to this guidelines, each activity layout will contain a “NavHostFragment” instance as a dynamic container:

<ViewGroup>

     <!--XXX: container used to display destinations in our graph -->
     <fragment
         android:id="@+id/main_activ_nav_container"
         android:name="androidx.navigation.fragment.NavHostFragment"
         app:defaultNavHost="true"
         app:navGraph="@navigation/home_graph"
         />

      <!-- TOP LEVEL NAV VIEWS HERE (toolbar, drawer, bottom nav...)

</ViewGroup>

Every one of these containers has associated (or “hosts”…) a navigation controller, which is responsible for executing navigation actions. This association is done through the “app:navGraph” property, so the fragment will display destinations listed on the specified graph.

On the other hand, the “app:defaultNavHost” property is used to make this component responsible (or not) for handling the up/back button behaviour.

“Every navhostfragment is associated to a navigation controller so it dynamically displays destination contents as the user navigates through the app”

Handling the navigation graph

Graph creation

The graph is a XML resource stored in the “res/navigation” folder. We can create both the directory and the XML file using the IDE contextual menu.

<navigation
   android:id="@+id/home_nav_graph.xml"
   >
  
   <!-- destinations here... >   

</navigation>

Graph configuration

Every graph needs an entry point: that’s the initial destination that will be displayed when launching navigation. It’s set with the “app:startDestination” property:

<navigation
   android:id="@+id/home_nav_graph.xml"
   app:startDestination="@+id/home_dest"
   >

   <!-- CONTENT DISPLAYED ON START ->
   <fragment      
android:id="@+id/home_dest"
      ...
      />

</navigation>

Adding destinations to graph

In order to add destinations (activities, fragments, views or dialogs) to the graph, we can use the drag-and-drop tools or code the corresponding XML tags.

Adding destination to the navigation graph with Android Studio

Every destination listed in the navigation graph must have the following attributes:

  • a unique ID, used by the system to identify screens and display them in response to user actions (such as selecting an option from a menu)
  • a name, containing the complete identifier of the class being represented by that destination
  • a label, used to show a title on the action bar
<navigation>

   <!-- CONTENT DISPLAYED ON START ->
   <fragment
      android:id="@+id/home_dest"
      android:name="some.package.HomeFragment"
      android:label="Home"
      ...
      />

   <!-- more destinations here... >   

</navigation>

Adding actions on destinations

Destinations can have nested actions, setting the places we can get to from the current screen. Every action specifies a destination using the “app:destination” property:

<navigation>

   <!-- CONTENT DISPLAYED ON START ->
   <fragment
      android:id="@+id/home_dest"
      ...
      />

      <action
         android:id="@+id/next_action"
         app:destination="@id/some_other_dest"
         />

   <!-- more destinations here... >   

</navigation>

Action linking 2 destinations

“By using destinations and its nested actions, we build up the app navigation blueprint”

Hooking UI events and navigation

In general terms, we can apply navigation when responding to events:

  • on views, like buttons, that usually perform secondary navigation (going from one screen to the next)
  • on top-level widgets, such as the bottom navigation view, that handle primary navigation (switching from one module to another in our app). Widgets for primary navigation usually have a XML menu associated.

On views

Let’s say we have, for instance, a simple button on a fragment layout:

<RelativeLayout
    ...
    tools:context=".flow.FlowFragmentStep1">

    <Button
        android:id="@+id/step1_main_btn"
        ...
        />

    <!-- XXX: more content here.. -->

</RelativeLayout>

When configuring the button click listener, we can call navigation controller methods to jump to another screen, specifying the specific destination in the parameter:

private fun setListeners() = 
   this.step1_main_btn.setOnClickListener { 
      this.findNavController().navigate(R.id.flow_step_2_dest) 
   }

As an alternative, we can also use a convenience method from the “Navigation” class:

Navigation.createNavigateOnClickListener(destination, args) 

On top-level widgets

If we want to prepare the action bar for navigation, first we create a configuration object containing:

  • the list of screens (identified by its destination name) on which we want to display the hamburguer icon (the rest will display the back icon)
  • the drawer layout associated with the toolbar. Will be shown when clicking the menu icon
  • the default behaviour for back navigation when clicking the back icon

After that, we call an extension method on the “Toolbar” class that actually binds the toolbar with our navigation graph:

private fun hookToolbar() {

   //XXX: create config object
   val config = AppBarConfiguration(

      //XXX: screens on which we show burguer icon
      setOf(R.id.home_dest, R.id.flow_step_1_dest, R.id.settings_dest),

      this.getDrawerLayout(),
   
      //XXX: behavior for up navigation
      fallbackOnNavigateUpListener = { this.getNavController().navigateUp()}
   )

   //XXX: configure toolbar and navigation using config
   this.app_toolbar.setupWithNavController(this.getNavController(), config)
}

private fun getDrawerLayout() = this.findViewById<DrawerLayout>(R.id.drawer)

protected fun getNavController() = this.findNavController()

On the other hand, if we have lateral navigation listing the following XML items:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

   <item     
      android:id="@+id/home_dest"
      android:title="@string/section_home"
      />

   <item
      android:id="@+id/flow_graph"
      android:title="@string/section_flow"
      />

   //XXX: more items...

</menu>

Then we can configure this widget too, using a similar extension function:

private fun hookPrimaryNavigation() {
   this.app_navigation?.setupWithNavController(navController = this.getNavController())
}

protected fun getNavController() = this.findNavController()

The Java version of the library forsakes these extensions functions with static methods. Regardless of the implementation language (Java or Kotlin), all these APIs perform the same operations, taking advantage of the shared IDs used both as destinations and menu items.

“Extension functions defined on view classes allow us to easily bind UI components and navigation”

Coming up next…

In the last post about Navigation Components, we will overlook some of its additional features. Until then, check the code in the following repo:

https://github.com/begomez/NavComponentsGarden

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: