Android Kotlin Fundamentals: 03.2 Define navigation paths

1. Welcome

In the previous codelab, you modified the AndroidTrivia app to add a Fragment to an existing activity. In this codelab, you add navigation to that app.

Introduction to navigation

Structuring the user's experience of navigating through an app has always been an interesting topic for developers. For Android apps, the Navigation Architecture Component makes it easier to implement navigation.

A destination is any place inside the app to which a user can navigate. A navigation graph for an app consists of a set of destinations within the app. Navigation graphs allow you to visually define and customize how users navigate among destinations in your app.

What you should already know

  • The fundamentals of Kotlin
  • How to create basic Android apps in Kotlin
  • How to work with layouts

What you'll learn

  • How to use navigation graphs
  • How to define navigation paths in your app
  • What an Up button is, and how to add one
  • How to create an options menu
  • How to create a navigation drawer

What you'll do

  • Create a navigation graph for your fragments using the navigation library and the Navigation Editor.
  • Create navigation paths in your app.
  • Add navigation using the options menu.
  • Implement an Up button so that the user can navigate back to the title screen from anywhere in the app.
  • Add a navigation drawer menu.

2. App overview

The AndroidTrivia app, which you started working on in the previous codelab, is a game in which users answer questions about Android development. If the user answers three questions correctly, they win the game.

If you completed the previous codelab, use that code as the starter code for this codelab. Otherwise, download the AndroidTriviaFragment app from GitHub to get the starter code.

In this codelab, you update the AndroidTrivia app in the following ways:

  • You create a navigation graph for the app.
  • You add navigation for a title screen and a game screen.
  • You connect the screens with an action, and you give the user a way to navigate to the game screen by tapping Play.
  • You add an Up button, which is shown as the left-arrow at the top of some screens.

ece0616ceed0570e.png b371e021d91566aa.png 646a0e9260a0485a.png

3. Task: Add navigation components to the project

Step 1: Add navigation dependencies

The Navigation component is a library that can manage complex navigation, transition animation, deep linking, and compile-time checked argument passing between the screens in your app.

To use the navigation library, you need to add the navigation dependencies to your Gradle files.

  1. To get started, download the AndroidTriviaFragment starter app or use your own copy of the AndroidTrivia app from the previous codelab. Open the AndroidTrivia app in Android Studio.
  2. In the Project: Android pane, open the Gradle Scripts folder. Double-click the project-level build.gradle file to open the file.

214ecb6d43abaad.png

  1. At the top of the project-level build.gradle file, along with the other ext variables, add a variable for the navigationVersion. To find the latest navigation version number, see Declaring dependencies in the Android developer documentation.
ext {
        ...
        navigationVersion = "2.3.0"
        ...
    }
  1. In the Gradle Scripts folder, open the module-level build.gradle file. Add the dependencies for navigation-fragment-ktx and navigation-ui-ktx, as shown below:
dependencies {
  ...
  implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
  implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
  ...
}
  1. Rebuild the project.

Step 2: Add a navigation graph to the project

  1. In the Project: Android pane, right-click the res folder and select New > Android Resource File.
  2. In the New Resource File dialog, select Navigation as the Resource type.
  3. In the File name field, name the file navigation.
  4. Make sure the Chosen qualifiers box is empty, and click OK. A new file, navigation.xml, appears in the res > navigation folder. 93fe19180970c3d7.png
  5. Open the res > navigation > navigation.xml file and click the Design tab to open the Navigation Editor. Notice the No NavHostFragments found message in the layout editor. You fix this problem in the next task. 518b79d0d8d840bf.png

4. Task: Create the NavHostFragment

A navigation host fragment acts as a host for the fragments in a navigation graph. The navigation host Fragment is usually named NavHostFragment.

As the user moves between destinations defined in the navigation graph, the navigation host Fragment swaps fragments in and out as necessary. The Fragment also creates and manages the appropriate Fragment back stack.

In this task, you modify your code to replace the TitleFragment with the NavHostFragment.

  1. Open res > layout > activity_main.xml and open the Code tab.
  2. In the activity_main.xml file, change the name of the existing title Fragment to androidx.navigation.fragment.NavHostFragment.
  3. Change the ID to myNavHostFragment.
  4. The navigation host Fragment needs to know which navigation graph resource to use. Add the app:navGraph attribute and set it to the navigation graph resource, which is @navigation/navigation.
  5. Add the app:defaultNavHost attribute and set it to "true". Now this navigation host is the default host and will intercept the system Back button.

Inside the activity_main.xml layout file, your fragment now looks like the following:

<!-- The NavHostFragment within the activity_main layout -->
            <fragment
                android:id="@+id/myNavHostFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:navGraph="@navigation/navigation"
                app:defaultNavHost="true" />

5. Task: Add fragments to the navigation graph

In this task, you add the title Fragment and the game Fragment to the app's navigation graph. You connect the fragments to each other. Then you add a click handler to the Play button so that the user can navigate from the title screen to the game screen.

Step 1: Add two fragments to the navigation graph and connect them with an action

  1. Open navigation.xml from the navigation resource folder. In the Navigation Editor, click the New Destination button 42ba2fe9605c8c71.png. A list of fragments and activities appears.

a6332911a93c252a.png

  1. Select fragment_title. You add fragment_title first because the TitleFragment is where app users start when they first open the app.

3b504eab00ee3c3b.png

a881f5057f83b8b9.png

  1. Use the New Destination button to add the GameFragment.

If the preview shows a "Preview Unavailable" message, click the Code tab to open the navigation XML. Make sure that the fragment element for the gameFragment includes tools:layout="@layout/fragment_game", as shown below.

<!-- The game fragment within the navigation XML, complete with tools:layout. -->
<fragment
   android:id="@+id/gameFragment"
   android:name="com.example.android.navigation.GameFragment"
   android:label="GameFragment"
   tools:layout="@layout/fragment_game" />
  1. In the layout editor (using the Design view), drag the gameFragment to the right so it doesn't overlap with the title Fragment.

55b926e07056fe7c.png

  1. In the preview, hold the pointer over the title Fragment. A circular connection point appears on the right side of the Fragment view. Click the connection point and drag it to the gameFragment or drag it to anywhere in the gameFragment preview. An Action is created that connects the two fragments.
  2. To see the Action's attributes, click the arrow that connects the two fragments. In the Attributes pane, check that the action's ID is set to action_titleFragment_to_gameFragment. 84c12b4ae2f079dc.png

Step 2: Add a click handler to the Play button

The title Fragment is connected to the game Fragment by an action. Now you want the Play button on the title screen to navigate the user to the game screen.

  1. In Android Studio, open the TitleFragment.kt file. Inside the onCreateView() method, add the following code before the return statement:
binding.playButton.setOnClickListener{}
  1. Inside setOnClickListener, add code to access the Play button through the binding class and navigate to the game fragment:
//The complete onClickListener with Navigation
binding.playButton.setOnClickListener { view : View ->
       view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}
  1. Build the app and make sure that it has all the imports it needs. For example, you might need to add the following line to the TitleFragment.kt file:
import androidx.navigation.findNavController
  1. Run the app and tap the Play button on the title screen. The game screen opens. d63117ee8c4b53a2.png

6. Task: Add conditional navigation

In this step, you add conditional navigation, which is navigation that's only available to the user in certain contexts. A common use case for conditional navigation is when an app has a different flow, depending on whether the user is logged in.

Your app is a different case: Your app will navigate to a different Fragment, based on whether the user answers all the questions correctly.

The starter code contains two fragments for you to use in your conditional navigation:

  • The GameWonFragment takes the user to a screen that shows a "Congratulations!" message.
  • The GameOverFragment takes the user to a screen that shows a "Try Again" message.

Step 1: Add GameWonFragment and GameOverFragment to the navigation graph

  1. Open the navigation.xml file, which is in the navigation folder.
  2. To add the game-over Fragment to the navigation graph, click the New Destination button 2b29197914d439c1.png in the Navigation Editor and select fragment_game_over. 2d1ae360738ef81c.png
  3. In the preview area of the layout editor, drag the game-over Fragment to the right of the game Fragment so the two don't overlap. Make sure to change the ID attribute of the game-over Fragment, to gameOverFragment.
  4. To add the game-won Fragment to the navigation graph, click the New Destination button 2b29197914d439c1.png and select fragment_game_won.

c92382cbd1c187d0.png

  1. Drag the game-won Fragment below the game-over Fragment so the two don't overlap. Make sure to name the ID attribute of the game-won Fragment as gameWonFragment.

The Layout Editor now looks something like the following screenshot: 2159ea574fe6a166.png

Step 2: Connect the game Fragment to the game-result Fragment

In this step, you connect the game Fragment to both the game-won Fragment and the game-over Fragment.

  1. In the preview area of the Layout Editor, hold the pointer over the game Fragment until the circular connection point appears.
  2. Click the connection point and drag it to the game-over Fragment. A blue connection arrow appears, representing an Action that now connects the game Fragment to the game-over Fragment.
  3. In the same way, create an action that connects the game Fragment to the game-won Fragment. The Layout Editor now looks something like the following screenshot: 5b130d64997393d5.png
  4. In the preview, hold the pointer over the line that connects the game Fragment to the game-won Fragment. Notice that the ID for the Action has been assigned automatically.

Step 3: Add code to navigate from one Fragment to the next

GameFragment is a Fragment class that contains questions and answers for the game. The class also includes logic that determines whether the user wins or loses the game. You need to add conditional navigation in the GameFragment class, depending on whether the player wins or loses.

  1. Open the GameFragment.kt file. The onCreateView() method defines an if / else condition that determines whether the player has won or lost:
binding.submitButton.setOnClickListener @Suppress("UNUSED_ANONYMOUS_PARAMETER")
        { 
              ...
                // answer matches, we have the correct answer.
                if (answers[answerIndex] == currentQuestion.answers[0]) {
                    questionIndex++
                    // Advance to the next question
                    if (questionIndex < numQuestions) {
                        currentQuestion = questions[questionIndex]
                        setQuestion()
                        binding.invalidateAll()
                    } else {
                        // We've won!  Navigate to the gameWonFragment.
                    }
                } else {
                    // Game over! A wrong answer sends us to the gameOverFragment.
                }
            }
        }
  1. Inside the else condition for winning the game, add the following code, which navigates to the gameWonFragment. Make sure that the Action name (action_gameFragment_to_gameWonFragment in this example) exactly matches what's set in the navigation.xml file.
// We've won!  Navigate to the gameWonFragment.
view.findNavController()
   .navigate(R.id.action_gameFragment_to_gameWonFragment)
  1. Inside the else condition for losing the game, add the following code, which navigates to the gameOverFragment:
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().
   navigate(R.id.action_gameFragment_to_gameOverFragment)
  1. Run the app and play the game by answering the questions. If you answer all three questions correctly, the app navigates to the GameWonFragment.

9a16d77b47272733.png

If you get any question wrong, the app immediately navigates to the GameOverFragment.

938a0650139026e3.png

The Android system's Back button is shown as 1 in the screenshot above. If the user presses the Back button in the game-won fragment or the game-over Fragment, the app navigates to the question screen. Ideally, the Back button should navigate back to the app's title screen. You change the destination for the Back button in the next task.

7. Task: Change the Back button's destination

The Android system keeps track of where users navigate on an Android-powered device. Each time the user goes to a new destination on the device, Android adds that destination to the back stack.

When the user presses the Back button, the app goes to the destination that's at the top of the back stack. By default, the top of the back stack is the screen that the user last viewed. The Back button is typically the left-most button at the bottom of the screen, as shown below. (The Back button's exact appearance is different on different devices.)

ad2f07d76d232c44.png

Until now, you've let the navigation controller handle the back stack for you. When the user navigates to a destination in your app, Android adds this destination to the back stack.

In the AndroidTrivia app, when the user presses the Back button from the GameOverFragment or GameWonFragment screen, they end up back in the GameFragment. But you don't want to send the user to the GameFragment, because the game is over. The user could restart the game, but a better experience would be to find themselves back at the title screen.

A navigation action can modify the back stack. In this task, you change the action that navigates from the game fragment so that the action removes the GameFragment from the back stack. When the user wins or loses the game, if they press the Back button, the app skips the GameFragment and goes back to the TitleFragment.

Step 1: Set the pop behavior for the navigation actions

In this step, you manage the back stack so that when the user is at the GameWon or GameOver screen, pressing the Back button returns them to the title screen. You manage the back stack by setting the "pop" behavior for the actions that connect the fragments:

  • The popUpTo attribute of an action "pops up" the back stack to a given destination before navigating. (Destinations are removed from the back stack.)
  • If the popUpToInclusive attribute is false or is not set, popUpTo removes destinations up to the specified destination, but leaves the specified destination in the back stack.
  • If popUpToInclusive is set to true, the popUpTo attribute removes all destinations up to and including the given destination from the back stack.
  • If popUpToInclusive is true and popUpTo is set to the app's starting location, the action removes all app destinations from the back stack. The Back button takes the user all the way out of the app.

In this step, you set the popUpTo attribute for the two actions that you created in the previous task. You do this using the Pop To field in the Attributes pane of the Layout Editor.

  1. Open navigation.xml in the res > navigation folder. If the navigation graph does not appear in the layout editor, click the Design tab.
  2. Select the action for navigating from the gameFragment to the gameOverFragment. (In the preview area, the action is represented by a blue line that connects the two fragments.)
  3. In the Attributes pane, set popUpTo to gameFragment. Select the popUpToInclusive checkbox.

49836803251ac3f9.png

This sets the popUpTo and popUpToInclusive attributes in the XML. The attributes tell the navigation component to remove fragments from the back stack up to and including GameFragment. (This has the same effect as setting the popUpTo field to titleFragment and clearing the popUpToInclusive checkbox.)

  1. Select the action for navigating from the gameFragment to the gameWonFragment. Again, set popUpTo to gameFragment in the Attributes pane and select the popUpToInclusive checkbox.

18326185bed734cf.png

  1. Run the app and play the game, then press the Back button. Whether you win or lose, the Back button takes you back to the TitleFragment.

Step 2: Add more navigation actions and add onClick handlers

Your app currently has the following user flow:

  • The user plays the game and wins or loses, and the app navigates to the GameWon or GameOver screen.
  • If the user presses the system Back button at this point, the app navigates to the TitleFragment. (You implemented this behavior in Step 1 of this task, above.)

In this step you implement two more steps of user flow:

  • If the user taps the Next Match or Try Again button, the app navigates to the GameFragment screen.
  • If the user presses the system Back button at this point, the app navigates to the TitleFragment screen (instead of back to the GameWon or GameOver screen).

To create this user flow, use the popUpTo attribute to manage the back stack:

  1. Inside the navigation.xml file, add a navigation action connecting gameOverFragment to gameFragment. Make sure that the Fragment names in the action's ID match the Fragment names that are in the XML. For example, the action ID might be action_gameOverFragment_to_gameFragment.

a26030570fa6c4c7.png

  1. In the Attributes pane, set the action's popUpTo attribute to titleFragment.
  2. Clear the popUpToInclusive checkbox, because you do not want the titleFragment to be included in the destinations that are removed from the back stack. Instead, you want everything up to the titleFragment (but not including it) to be removed from the back stack.

77cb8eeb22fee1d5.png

  1. Inside the navigation.xml file, add a navigation action connecting gameWonFragment to gameFragment.

d22457bbb25d3a80.png

  1. For the action you just created, set the popUpTo attribute to titleFragment and clear the popUpToInclusive checkbox.

7bb8085588103170.png

Now add functionality to the Try Again and Next Match buttons. When the user taps either button, you want the app to navigate to the GameFragment screen so that the user can try the game again.

  1. Open the GameOverFragment.kt Kotlin file. At the end of the onCreateView() method, before the return statement, add the following code. The code adds a click listener to the Try Again button. When the user taps the button, the app navigates to the game Fragment.
// Add OnClick Handler for Try Again button
        binding.tryAgainButton.setOnClickListener{view: View->
        view.findNavController()
                .navigate(R.id.action_gameOverFragment_to_gameFragment)}
  1. Open the GameWonFragment.kt Kotlin file. At the end of the onCreateView() method, before the return statement, add the following code:
// Add OnClick Handler for Next Match button
        binding.nextMatchButton.setOnClickListener{view: View->
            view.findNavController()
                    .navigate(R.id.action_gameWonFragment_to_gameFragment)}
  1. Run your app, play the game, and test the Next Match and Try Again buttons. Both buttons should take you back to the game screen so that you can try the game again.
  2. After you win or lose the game, tap Next Match or Try Again. Now press the system Back button. The app should navigate to the title screen, instead of going back to the screen that you just came from.

8. Task: Add an Up button in the app bar

The app bar

The app bar, sometimes called the action bar, is a dedicated space for app branding and identity. For example, you can set the app bar's color. The app bar gives the user access to familiar navigation features such as an options menu. To access the options menu from the app bar, the user taps the icon with the three vertical dots 17a623662cf21a51.png.

The app bar displays a title string that can change with each screen. For the title screen of the AndroidTrivia app, the app bar displays "Android Trivia." On the question screen, the title string also shows which question the user is on ("1/3," "2/3," or "3/3.")

The Up button

Currently in your app, the user uses the system Back button to navigate to previous screens. However, Android apps can also have an on-screen Up button that appears at the top left of the app bar.

In the AndroidTrivia app, you want the Up button to appear on every screen except the title screen. The Up button should disappear when the user reaches the title screen, because the title screen is at the top of the app's screen hierarchy.

Add support for an Up button

The navigation components include a UI library called NavigationUI. The Navigation component includes a NavigationUI class. This class contains static methods that manage navigation with the top app bar, the navigation drawer, and bottom navigation. The navigation controller integrates with the app bar to implement the behavior of the Up button, so you don't have to do it yourself.

In the following steps, you use the navigation controller to add an Up button to your app:

  1. Open the MainActivity.kt kotlin file. Inside the onCreate() method, add code to find the navigation controller object:
val navController = this.findNavController(R.id.myNavHostFragment)
  1. Also inside the onCreate() method, add code to link the navigation controller to the app bar:
NavigationUI.setupActionBarWithNavController(this,navController)
  1. After the onCreate() method, override the onSupportNavigateUp() method to call navigateUp() in the navigation controller:
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
  1. Run the app. The Up button appears in the app bar on every screen except the title screen. No matter where you are in the app, tapping the Up button takes you to the title screen.

You may see "fragment_title" in the upper left corner. Edit res>navigation>navigation.xml and change the label of com.example.android.navigation.TitleFragment from "fragment_title" to @string/app_name. And then define this resource in res>values>strings.xml as "Android Trivia" and re-run the app.

dbdd43591cef6957.png

9. Task: Add an options menu

Android has different types of menus, including the options menu. On modern Android devices, the user accesses the options menu by tapping three vertical dots 17a623662cf21a51.png that appear in the app bar.

In this task, you add an About menu item to the options menu. When the user taps the About menu item, the app navigates to the AboutFragment, and the user sees information about how to use the app.

Step 1: Add the AboutFragment to the navigation graph

  1. Open the navigation.xml file and click the Design tab to see the navigation graph.
  2. Click the New Destination button and select fragment_about. 4008092df17e0721.png
  3. In the layout editor, drag the "about" Fragment to the left so it doesn't overlap with the other fragments. Make sure the Fragment's ID is aboutFragment.

811c8eee505acb18.png

Step 2: Add the options-menu resource

  1. In the Android Studio Project pane, right-click the res folder and select New > Android Resource File.
  2. In the New Resource File dialog, name the file options_menu.
  3. Select Menu as the Resource type and click OK. e1d29c68cf051e2b.png
  4. Open the options_menu.xml file from the res > menu folder and click the Design tab to see the Layout Editor.
  5. From the Palette pane, drag a Menu Item (shown as 1 in the screenshot below) and drop it anywhere in the design editor pane (2). A menu item appears in the preview (3) and in the Component Tree (4). 4200941f0dff7549.png
  6. In the preview or in the Component Tree, click the menu item to show its attributes in the Attributes pane.
  7. Set the menu item's ID to aboutFragment. Set the title to @string/about. 258b01ff979266ff.png

Step 3: Add an onClick handler

In this step, you add code to implement behavior when the user taps the About menu item.

  1. Open the TitleFragment.kt Kotlin file. Inside the onCreateView() method, before the return, call the setHasOptionsMenu() method and pass in true.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
   ...
   setHasOptionsMenu(true)
   return binding.root
}
  1. After the onCreateView() method, override the onCreateOptionsMenu() method. In the method, add the options menu and inflate the menu resource file.
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  1. Override the onOptionsItemSelected() method to take the appropriate action when the menu item is tapped. In this case, the action is to navigate to the Fragment that has the same id as the selected menu item.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}
  1. If the app doesn't build, check to see whether you need to import packages to fix unresolved references in the code. For example, you can add import android.view.* to resolve several references (and replace more specific imports such as import android.view.ViewGroup).
  2. Run the app and test the About menu item that's in the options menu. When you tap the menu item, the app should navigate to the "about" screen.

1f9c01760bc90c91.png

10. Task: Add the navigation drawer

In this task, you add a navigation drawer to the AndroidTrivia app. The navigation drawer is a panel that slides out from the edge of the screen. The drawer typically contains a header and a menu.

On phone-sized devices, the navigation drawer is hidden when not in use. Two types of user actions can make the navigation drawer appear:

  • The drawer appears when the user swipes from the starting edge of the screen toward the ending edge of the screen. In the AndroidTrivia app, the navigation drawer appears when the user swipes from left to right.
  • The drawer appears when the user is at the start destination of the app and taps the drawer icon in the app bar. (The drawer icon is sometimes called the nav drawer button or hamburger icon 396398b45dcdfad.png.)

The screenshot below shows an open navigation drawer.

884b77b9b0e076cc.png

The navigation drawer is part of the Material Components for Android library, or Material library. You use the Material library to implement patterns that are part of Google's Material Design guidelines.

In your AndroidTrivia app, the navigation drawer will contain two menu items. The first item points to the existing "about" Fragment, and the second item will point to a new "rules" Fragment.

214070ba1235bb94.png

Step 1: Add the Material library to your project

  1. In the app-level Gradle build file, add the dependency for the Material library:
dependencies {
    ...
    implementation "com.google.android.material:material:$version"
    ...
}
  1. Sync your project.

Step 2: Make sure the destination fragments have IDs

The navigation drawer will have two menu items, each representing a Fragment that can be reached from the navigation drawer. Both destinations must have an ID in the navigation graph.

The AboutFragment already has an ID in the navigation graph, but the RulesFragment does not, so add it now:

  1. Open the fragment_rules.xml layout file to see what it looks like. Click the Design tab to look at the preview in the design editor.
  2. Open the navigation.xml file in the Navigation Editor. Click the New Destination button and add the rules Fragment. Set its ID to rulesFragment.

a345df07e0404165.png

e8965bee387c1ebd.png

Step 3: Create the drawer menu and the drawer layout

To create a navigation drawer, you create the navigation menu. You also need to put your views inside a DrawerLayout in the layout file.

  1. Create the menu for the drawer. In the Project pane, right-click the res folder and select New Resource File. Name the file navdrawer_menu, set the resource type to Menu, and click OK. a925786508c5b763.png
  2. Open navdrawer_menu.xml from the res > menu folder, then click the Design tab. Add two menu items by dragging them from the Palette pane into the Component Tree pane.
  3. For the first menu item, set the id to rulesFragment. (The ID for a menu item should be the same as the ID for the Fragment.) Set the title to @string/rules and the icon to @drawable/rules.

1f6185090dcc6a5a.png

  1. For the second menu item, set the id to aboutFragment, the title string to @string/about, and the icon to @drawable/about_android_trivia.

78db57be7adc9b17.png

  1. Open the activity_main.xml layout file. To get all the drawer functionality for free, put your views inside a DrawerLayout. Wrap the entire <LinearLayout> inside a <DrawerLayout>. (In other words, add a DrawerLayout as the root view.)
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <androidx.drawerlayout.widget.DrawerLayout
       android:id="@+id/drawerLayout"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

   <LinearLayout
       . . . 
       </LinearLayout>
   </androidx.drawerlayout.widget.DrawerLayout>
</layout>
  1. Now add the drawer, which is a NavigationView that uses the navdrawer_menu that you just defined. Add the following code in the DrawerLayout, after the </LinearLayout> element:
<com.google.android.material.navigation.NavigationView
   android:id="@+id/navView"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:layout_gravity="start"
   app:headerLayout="@layout/nav_header"
   app:menu="@menu/navdrawer_menu" />

Step 4: Display the navigation drawer

You created the menu items for the navigation drawer and the navigation drawer layout. Now you need to connect the navigation drawer to the navigation controller so that when users select items in the navigation drawer, the app navigates to the appropriate Fragment.

  1. Open the MainActivity.kt Kotlin file. In onCreate(), add the code that allows the user to display the navigation drawer. Do this by calling setupWithNavController(). Add the following code at the bottom of onCreate():
NavigationUI.setupWithNavController(binding.navView, navController)
  1. Run your app. Swipe from the left edge to display the navigation drawer, and make sure each of the menu items in the drawer goes to the right place.

Although the navigation drawer works, you need to fix one more thing. Typically apps also allow users to display the navigation drawer by tapping the drawer button (three lines) 396398b45dcdfad.png in the app bar on the home screen. Your app does not yet display the drawer button on the home screen.

Step 5: Display the navigation drawer from the drawer button

The final step is to enable the user to access the navigation drawer from the drawer button at the top left of the app bar.

  1. In the MainActivity.kt Kotlin file, add the lateinit drawerLayout member variable to represent the drawer layout:
    private lateinit var drawerLayout: DrawerLayout
  1. Inside the onCreate() method, initialize drawerLayout after the binding variable has been initialized.
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,
                R.layout.activity_main)

drawerLayout = binding.drawerLayout
  1. Add the drawerLayout as the third parameter to the setupActionBarWithNavController() method:
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
  1. Edit the onSupportNavigateUp() method to return NavigationUI.navigateUp instead of returning navController.navigateUp. Pass the navigation controller and the drawer layout to navigateUp(). The method will look like as follows:
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}
  1. You might need to add another import to the file so all the references resolve, for example:
import androidx.drawerlayout.widget.DrawerLayout
  1. Run your app. Swipe from the left edge to display the navigation drawer, and make sure each of the menu items in the drawer goes to the right place.
  2. Go to the home screen and tap the nav drawer button 396398b45dcdfad.png to make sure the navigation drawer appears. Make sure that clicking the Rules or About options in the navigation drawer takes you to the right place.

d3963d5bcb623d52.png

Congratulations!

You have now added several different navigation options to your app.

The user can now progress through the app by playing the game. They can get back to the home screen at any time by using the Up button. They can get to the About screen either from the Options menu or from the navigation drawer. Pressing the Back button takes them back through previous screens in a way that makes sense for the app. The user can open the navigation drawer by swiping in from the left on any screen, or by tapping the drawer button in the app bar on the home screen.

Your app includes robust, logical navigation paths that are intuitive for your user to use. Congratulations!

11. Solution code

Android Studio project: AndroidTriviaNavigation

12. Summary

To use the Android navigation library, you need to do some setup:

  • Add dependencies for navigation-fragment-ktx and navigation-ui-ktx in the module-level build.gradle file.
  • Add an ext variable for the navigationVersion in the project-level build.gradle file.

Navigation destinations are fragments, activities, or other app components that the user navigates to. A navigation graph defines the possible paths from one navigation destination to the next.

  • To create a navigation graph, create a new Android resource file of type Navigation. This file defines the navigation flow through the app. The file is in the res/navigation folder, and it's typically called navigation.xml.
  • To see the navigation graph in the Navigation Editor, open the navigation.xml file and click the Design tab.
  • Use the Navigation Editor to add destinations such as fragments to the navigation graph.
  • To define the path from one destination to another, use the Navigation Graph to create an action that connects the destinations. In the navigation.xml file, each of these connections is represented as an action that has an ID.

A navigation host Fragment, usually named NavHostFragment, acts as a host for the fragments in the navigation graph:

  • As the user moves between destinations defined in the navigation graph, the NavHostFragment swaps the fragments in and out and manages the Fragment back stack.
  • In the activity_main.xml layout file, the NavHostFragment is represented by a fragment element with the name android:name="androidx.navigation.fragment.NavHostFragment".

To define which Fragment is displayed when the user taps a view (for example a button), set the onClick listener for the view:

  • In the onClick listener, call findNavController().navigate() on the view.
  • Specify the ID of the action that leads to the destination.

Conditional navigation navigates to one screen in one case, and to a different screen in another case. To create conditional navigation:

  1. Use the Navigation Editor to create a connection from the starting Fragment to each of the possible destination fragments.
  2. Give each connection a unique ID.
  3. In the click-listener method for the View, add code to detect the conditions. Then call findNavController().navigate() on the view, passing in the ID for the appropriate action.

The Back button

The system's Back button is usually at the bottom of the device. By default, the Back button navigates the user back to the screen they viewed most recently. In some situations, you can control where the Back button takes the user:

  • In the Navigation Editor, you can use the Attributes pane to change an action's popUpTo setting. This setting removes destinations from the back stack, which has the effect of determining where the Back button takes the user.
  • The popUpTo setting appears as the popUpTo attribute in the navigation.xml file.

7bb8085588103170.png

  • Selecting the popUpToInclusive checkbox sets the popUpToInclusive attribute to true. All destinations up to and including this destination are removed from the back stack.
  • If an action's popUpTo attribute is set to the app's starting destination and popUpToInclusive is set to true, the Back button takes the user all the way out of the app.

The Up button

Screens in an Android app can have an on-screen Up button that appears at the top left of the app bar. (The app bar is sometimes called the action bar.) The Up button navigates "upwards" within the app's screens, based on the hierarchical relationships between screens.

The navigation controller's NavigationUI library integrates with the app bar to allow the user to tap the Up button on the app bar to get back to the app's home screen from anywhere in the app.

To link the navigation controller to the app bar:

  1. In onCreate(), call setupActionBarWithNavController() on the NavigationUI class, passing in the navigation controller:
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController)
  1. Override the onSupportNavigateUp() method to call navigateUp() in the navigation controller:
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
}

The options menu

The options menu is a menu that the user accesses from the app bar by tapping the icon with the three vertical dots 17a623662cf21a51.png. To create an options menu with a menu item that displays a Fragment, make sure the Fragment has an ID. Then define the options menu and code the onOptionsItemSelected() handler for the menu items.

  1. Make sure the Fragment has an ID:
  • Add the destination Fragment to the navigation graph and note the ID of the Fragment. (You can change the ID if you like.)
  1. Define the options menu:
  • Create an Android resource file of type Menu, typically named options_menu.xml. The file is stored in the Res > Menu folder.
  • Open the options_menu.xml file in the design editor and drag a Menu Item widget from the Palette pane to the menu.
  • For convenience, make the ID of the menu item the same as the ID of the Fragment to display when the user clicks this menu item. This step is not required, but it makes it easier to code the onClick behavior for the menu item.
  1. Code the onClick handler for the menu item:
  • In the Fragment or Activity that displays the options menu, in onCreateView(), call setHasOptionsMenu(true) to enable the options menu.
  • Implement onCreateOptionsMenu() to inflate the options menu:
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  • Override the onOptionsItemSelected() method to take the appropriate action when the menu item is clicked. The following code displays the Fragment that has the same ID as the menu item. (This code only works if the menu item and the Fragment have identical ID values.)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}

The navigation drawer

The navigation drawer is a panel that slides out from the edge of the screen. There are two ways for the user to open the navigation drawer:

  • Swipe from the starting edge (usually the left) on any screen.
  • Use the drawer button (three lines) 396398b45dcdfad.png on the app bar at the top of the app.

To add a navigation drawer to your app:

  1. Add dependencies to build.gradle (app).
  2. Make sure each destination Fragment has an ID.
  3. Create the menu for the drawer.
  4. Add the drawer to the layout for the Fragment.
  5. Connect the drawer to the navigation controller.
  6. Set up the drawer button in the app bar.

These steps are explained in more detail below.

  1. Add dependencies to build.gradle:
  • The navigation drawer is part of the Material Components for Android library. Add the Material library to the build.gradle (app) file:
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}
  1. Give each destination Fragment an ID:
  • If a Fragment is reachable from the navigation drawer, open it in the navigation graph to make sure that it has an ID.
  1. Create the menu for the drawer:
  • Create an Android resource file of type Menu (typically called navdrawer_menu) for a navigation drawer menu. This creates a new navdrawer_menu.xml file in the Res > Menu folder.
  • In the design editor, add Menu Item widgets to the Menu.
  1. Add the drawer to the layout for the Fragment:
  • In the layout that contains the navigation host Fragment (which is typically the main layout), use <androidx.drawerlayout.widget.DrawerLayout> as the root view.
  • Add a <com.google.android.material.navigation.NavigationView> view to the layout.
  1. Connect the drawer to the navigation controller:
  • Open the Activity that creates the navigation controller. (The main Activity is typically the one you want here.) In onCreate(), use NavigationUI.setupWithNavController()to connect the navigation drawer with the navigation controller:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)
NavigationUI.setupWithNavController(binding.navView, navController)
  1. Set up the drawer button in the app bar:
  • In onCreate() in the Activity that creates the navigation controller (which is typically the main Activity), pass the drawer layout as the third parameter to NavigationUI.setupActionBarWithNavController:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
    this, R.layout.activity_main)

NavigationUI.setupActionBarWithNavController(
    this, navController, binding.drawerLayout)
  • To make the Up button work with the drawer button, edit onSupportNavigateUp() to return NavigationUI.navigateUp(). Pass the navigation controller and the drawer layout to navigateUp().
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}

13. Learn more

Udacity course:

Android developer documentation:

Material Design documentation:

14. Homework

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

  • Assign homework if required.
  • Communicate to students how to submit homework assignments.
  • Grade the homework assignments.

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Add a Rules button and an About button to the layout for the TitleFragment as shown below. When the user taps the Rules or About button, the app navigates to the RulesFragment or AboutFragment, as appropriate.

c2b26f43e769582c.png

For extra credit:

On each of the layouts for the RulesFragment and the AboutFragment, add a Play button that navigates to the GameFragment as shown here:

dc634d0c2034e79.png 77f174207641d6f7.png

Hint: You need to update the layouts for these two fragments to enable data binding.

Change the back stack to enable the following user flow:

  1. The user taps the Play button to go from the RulesFragment screen or the AboutFragment screen to the GameFragment.
  2. Then the user taps the Back button at the bottom of the screen.
  3. The app navigates back to the TitleFragment, not to the RulesFragment or AboutFragment.

In other words, when the user taps the Play button while viewing the rules or the information about the game, then taps the Back button, the RulesFragment or the AboutFragment is removed from the back stack.

Answer these questions

Question 1

How do you enable your project to use navigation components?

  • Make sure every Activity class extends the class NavigationActivity.
  • Use the NavigationController class as the launch Activity.
  • Add <uses-navigation> to the Android manifest file.
  • Add dependencies for navigation-fragment-ktx and navigation-ui-ktx in the build.gradle (module) file.

Question 2

Where are the possible routes through your app defined?

  • In a file (often called navigation.xml) in the res > layout folder.
  • In a file (often called navigation.xml) in the app > navigation folder.
  • In a file (often called navigation.xml) in the res > navigation folder.
  • In the android-manifest.xml file in the <navigation> element.

Question 3

Which of the following statements about the NavHostFragment are true? Select all that apply.

  • As the user moves between destinations defined in the navigation graph, the NavHostFragment swaps the fragments in and out as necessary.
  • You can click the NavHostFragment in the Project view to open the navigation graph.
  • You add the NavHostFragment to the main layout by adding a <fragment> whose name is androidx.navigation.fragment.NavHostFragment.
  • You must create a single NavHostFragment subclass and implement the onNavigate() method to handle different kinds of navigation (such as button clicks).

Question 4

Which of the following statements about the navigation graph are true? Select all that apply.

  • You create a potential path through the app from one Fragment to another by connecting the fragments in the navigation graph.
  • The type of a connection between fragments is Action.
  • You must add the type="navigation" attribute to every <fragment> that is included in the navigation graph.
  • To open the navigation graph, you double-click the navigation file (navigation.xml) in the Android Studio Project pane, then click the Design tab.

Question 5

Where do you set the ID of a Fragment to be used in navigation?

  • In the Fragment's layout file, either by setting the ID attribute in the design editor or in the layout XML file in the res > layout folder.
  • In the project's navigation file, either by setting the ID attribute in the navigation graph or in the navigation XML file in the res > navigation folder.
  • You need to set the ID in both the navigation file for the app and the layout file for the Fragment.
  • Set the ID variable in the relevant Fragment class.

Question 6

The News app has a NewsFragment that displays a Show headlines button. The goal is that when the user clicks this button, the app navigates to the HeadlinesFragment.

Assume that you've added a connection from the NewsFragment to the HeadlinesFragment in the navigation graph, as shown here:

ed3a0c60382be5e2.png

What else do you need to do so that when the user taps the Show headlines button, the app navigates to the HeadlinesFragment?

  • Actually, you don't need to do anything else. It's enough to set the navigation paths in the navigation graph.
  • In the onclickListener for the Show headlines button, call navigate() on the navigation controller, passing the class name of the destination Fragment (in this case HeadlinesFragment) .
  • In the onclickListener for the Show headlines button, call navigate() on the navigation controller, passing the action that connects the NewsFragment to the HeadlinesFragment.
  • In the onclickListener for the Show headlines button, call navigateTo() on the container Fragment, passing the class name of the destination Fragment (in this case HeadlinesFragment) .

Question 7

When users navigate through an app, sometimes they want to retrace their steps back through the screens they have already visited.

Assume the following:

  • fragmentA is connected to fragmentB by action_FragmentA_to_FragmentB.
  • fragmentB is connected to fragmentC by action_FragmentB_to_FragmentC

e51a8cb14ea64673.png

Which of the following statements are true regarding navigating forward and backward through the app? (Choose all that apply.)

  • The destination of the action_FragmentA_to_FragmentB action specifies that when the user is at FragmentA, the next destination in the app is FragmentB.
  • The destination of the action_FragmentA_to_FragmentB action sets the next destination that the user goes to, whether the user taps a button in the app or taps the Back button at the bottom of the screen.
  • The popUpTo attribute of the action can modify where the app navigates to if the user taps the system Back button.
  • The popUpTo attribute of the action can modify where the user goes next as they navigate forward through the app.

Question 8

When users navigate through an app, sometimes they want to retrace their steps back through the screens they have already visited. However, you can use the popUpTo and popUpToInclusive attributes of an action to modify the path backward through the app.

Assume the following:

  • fragmentA is connected to fragmentB by action_FragmentA_to_FragmentB.
  • fragmentB is connected to fragmentC by action_FragmentB_to_FragmentC.

6687a2b5f1fb22fb.png

The user navigates from fragmentA to fragmentB to fragmentC, then taps the system Back button. In this situation, let's say you want the app to navigate back to fragmentA (instead of fragmentB). What's the correct way to set the popUpTo and popUpToInclusive attributes?

  • For action_FragmentA_to_FragmentB, set popUpTo to fragmentB and popUpToInclusive to no value. For action_FragmentB_to_FragmentC, set popUpTo to fragmentA and popUpToInclusive to true.
  • For action_FragmentA_to_FragmentB, set popUpTo to fragmentA and popUpToInclusive to true. For action_FragmentB_to_FragmentC, set popUpTo to fragmentA and popUpToInclusive to true.
  • For action_FragmentA_to_FragmentB, set popUpTo to none and popUpToInclusive to no value. (You can omit both attributes.) For action_FragmentB_to_FragmentC, set popUpTo to fragmentA and popUpToInclusive to true.
  • For action_FragmentA_to_FragmentB, set popUpTo to none and popUpToInclusive to no value. (You can omit both attributes.) For action_FragmentB_to_FragmentC, set popUpTo to fragmentA and popUpToInclusive to false.

Question 9

Assume that the action action_headlinesFragment_to_newsArticle in the destination graph has a popUpTo value of newsFragment:

b8d390e2844e7bf.png

Assume that the user opens the app and navigates through the screens in the following sequence (without using the Back button):

Open app into News home > Headlines > News details

When the user is viewing the News detail screen, what happens If they tap the system Back button at the bottom of the screen?

Select all that apply (remembering that popUpTo is newsFragment).

  • If popUpToInclusive is true: Android navigates out of the app because there is nothing left in the back stack for this app.
  • If popUpToInclusive is false: The app goes back to the news home screen.
  • If popUpToInclusive is true: The app goes back to the news home screen.
  • If popUpToInclusive is false: The app goes back to the headlines screen.

Question 10

Where do you define the items for a menu?

  • It depends on where the menu will be shown. For a navigation drawer menu, add an <item> tag for each menu item in the menu.xml file in res > drawer folder. For the options menu, add an <item> tag for each menu item in the menu.xml file in res > options folder.
  • In the layout file for the Fragment or Activity that displays the menu, add a <menu> tag that contains <item> tags for each item.
  • In a menu_name.xml file in the res > menu folder, add an <item> tag for each menu item. Create separate XML files for each separate menu.
  • In the android_manifest.xml file, add a <menus> tag that contains a <menu> tag for each menu. For each <menu> tag, add an <item> tag for each menu item.

Question 11

To enable the options menu in your app, you need to define the menu items. Then what do you need to do in the Activity or Fragment where the options menu is to appear?

Select all that apply:

  • Call setHasOptionsMenu(true) in onCreate() for an Activity, or in onCreateView() for a Fragment.
  • Implement onCreateOptionsMenu() in the Activity or Fragment to create the menu.
  • Set the onClick attribute in the menu's XML file to onShowOptionsMenu, unless you are implementing a custom onClick listener for the options menu, in which case specify the name of the custom onClick listener instead.
  • Implement onOptionsItemSelected() in the Activity or Fragment to determine what happens when a user selects a menu item in the options menu.

Question 12

What do you need to do to enable a navigation drawer in your app? You can assume that your project is using the navigation graph and that you've already defined the menu items.

Select all that apply:

  • Use <DrawerLayout> as the root view in the relevant layout file, and add a <NavigationView> tag to that layout.
  • Use <Navigation> as the root view in the relevant layout file, and add a <NavigationView> tag to that layout.
  • In the <NavigationView> in the layout, set the android:menu attribute to the navigation drawer menu.
  • In the navigation XML file, make sure that the navigation menu has an ID.

Question 13

Following on from the previous question, you need to write some code to enable the navigation drawer to be displayed when the user swipes in from the left side of the screen?

In onCreate() within the Activity that creates the navigation controller, what is the right code to add?

  • A:
NavigationUI.setupWithNavController(
    navigationLayoutID, navigationMenuID)
  • B:
NavigationUI.setupWithNavController(
    navigationView, navigationController)
  • C:
NavigationDrawer.setupWithNavInterface(
    navigationView, navigationController)
  • D:
NavigationDrawer.setupWithNavController(
    navigationView, navigationMenuID)

Question 14

How do you add support for the Up button at the top of the screen to enable users to get back to the app's home screen from anywhere in the app? What do you need to do in the relevant Activity?

Select all that apply:

  • In the res > menu folder, create the up_menu.xml file.
  • Link the navigation controller to the app bar using NavigationUI.setupActionBarWithNavController(context,navigationController)
  • Override onSupportNavigateUp() method to call navigateUp() on the navigation controller.
  • In the navigation graph, make sure the starting Fragment has an ID of HomeFragment.

Submit your app for grading

Guidance for graders

Check that the app has the following features:

  • The TitleFragment screen has a Rules and About button as shown below.
  • When the user taps the Rules or About button, the app navigates to the RulesFragment or AboutFragment, as appropriate.

c2b26f43e769582c.png

For extra credit:

The RulesFragment and AboutFragment screens both have a Play button that navigates to the GameFragment as shown here:

dc634d0c2034e79.png 77f174207641d6f7.png

Check that the layouts for these two fragments both have data binding enabled.

Check the following user flow:

  1. Tap the Play button to go from the rules-fragment screen or the about-fragment screen to the game Fragment.
  2. Press the system Back button at the bottom of the screen.

The app should navigate back to the title Fragment, not to the rules Fragment or the about Fragment.

15. Next codelab

Start the next lesson:

For links to other codelabs in this course, see the Android Kotlin Fundamentals codelabs landing page.