Integrating NavigationDrawer with NavigationComponent

This tutorial shows you how to integrating Navigation Component with Toolbar as ActionBar and NavigationDrawer.

NavigationUI class

Navigation Component includes a NavigationUI class. This class contains static methods for applying modifications to the top app bar and/or bottom app bar according to the user's navigation through the app. For example, this class automatically updates as users navigate through your app:

  • Top app bar's content (title and options menu);
  • Top app bar's navigation button (hamburger icon for top-levels and a left-arrow for others when utilizing NavigationDrawer);
  • Bottom navigation bar's selected item. 

AppBarConfiguration


NavigationUI uses an AppBarConfiguration object to manage the behavior of the Navigation Button in the upper-left corner of your app's display area. Top-level destinations doesn't show a Navigation Button because is no higher level destination to go back, except when app utilizes NavigationDrawer, in this case, the hamburger icon is showed, indicating that it is possible to open a Navigation Menu . Lower-level destinations shows a left-arrow icon as a Navigation Button, indicating that it is possible to return to the previous destination. 

To configure the Navigation button using only the start destination as the top-level destination, create an AppBarConfiguration object, and pass in the corresponding navigation graph, as shown below:

val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

In some cases, you might need to define multiple top-level destinations instead of using the default start destination. Using a BottomNavigationView is a common use case for this, where you may have sibling screens that are not hierarchically related to each other and may each have their own set of related destinations. For cases like these, you can instead pass a set of destination IDs to the constructor, as shown below:
val appBarConfiguration = AppBarConfiguration(setOf(R.id.main, R.id.profile))

Steps to integrate NavigationDrawer with NavigationComponent


1. Create NavigationDrawer. In a nutshell, create a menu resource for Navigation Menu, create a DrawerLayout with a main content (that can be any normal layout types as LinearLayout or ConstraintLayout) and a NavigationView, and link NavigationView with menu resource with the tag app:menu. More details about NavigationDrawer and its implementation here.

2. Create a Toolbar and set this as ActionBar. Inside the main content of the DrawerLayout, creates a toolbar, as below:
<LinearLayout <!-- Main UI Content -->
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@android:color/white"
android:background="?attr/colorPrimary"/>
</LinearLayout>

And in the Activity's onCreate(), set this Toolbar as the app's ActionBar:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
}

More details about Toolbar, ActionBar and its implementations, see here.


3. Create Navigation Component. In a nutshell, create navigation graph, add NavHost to the Activity's layout (make sure that the NavHost is contained in the layout that represents the main content inside the DrawerLayout, otherwise the Navigation Menu may not be displayed over the main layout when the user clicks Navigation Button), resulting:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
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/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.MainActivity">
    <LinearLayout <!-- Main UI Content -->
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="@android:color/white"
android:background="?attr/colorPrimary"/>

<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" />
</LinearLayout>

<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:clipToPadding="false"
android:fitsSystemWindows="true"
app:menu="@menu/main_menu"
/>

</androidx.drawerlayout.widget.DrawerLayout>

4. Link NavigationView, NavController and NavigationUI. We need to setup NavController to orchestrates the swapping of fragments in the NavHost, based on selected menu option in the NavigationView (the ViewGroup for Navigation Menu). When utilizing Navigation Component, NavigationUI will open and close NavigationView and update the visual of the selected menu. All this links (NavigationView, NavController and NavigationUI) is made on Activity's onCreate(): 
class MainActivity : AppCompatActivity() {

private lateinit var drawerLayout: DrawerLayout
private lateinit var navController: NavController
private lateinit var appBarConfiguration:AppBarConfiguration

override fun onCreate(savedInstanceState: Bundle?) {

   ...


        drawerLayout = findViewById<DrawerLayout>(R.id.drawer)
        val navHostFragment = 
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController
        findViewById<NavigationView>(R.id.navigation_view)
.
setupWithNavController(navController)
}
}

These lines of code link NavigationView for use with NavController and gives NavigationUI the responsibility to control visual updates (close the NavigationView, update the selected item in the Navigation Menu of the NavigationView).

More details about Navigation Component and its implementation here.


5. Informs NavigationUI that you use NavigationDrawer, since, in this case, NavigationUI will have to control the NavigationDrawer hamburger icon. This is done by creating an AppBarConfiguration in the following way: 

appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)


6. As we are utilizing a Toolbar as ActionBar, we need to call  setupActionBarWithNavController() from our main activity's onCreate() method in this way:

setupActionBarWithNavController(navController, appBarConfiguration)

By calling this method, the title in the action bar will automatically be updated by NavigationUI when the NavController changes the current destination. The AppBarConfiguration we provide controls how the Navigation Button is displayed.

And we need to override method onSupportNavigateUp() to handle Up navigation as below:

override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}


Finally, our Activity's class is as below:
class MainActivity : AppCompatActivity() {

private lateinit var drawerLayout: DrawerLayout
private lateinit var navController: NavController
private lateinit var appBarConfiguration:AppBarConfiguration

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.
activity_main)
        setSupportActionBar(findViewById(R.id.toolbar))

drawerLayout = findViewById<DrawerLayout>(R.id.drawer)
val navHostFragment =

            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
        findViewById<NavigationView>(R.id.navigation_view)
.
setupWithNavController(navController)

appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
}

override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration)
||
super.onSupportNavigateUp()
}
    /*
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
    */
}

Note that the optional overriding of the method onBackPressed(). This can be done if you want that when user clicks on back button, the Navigation Menu closes.

But how clicks on an item of the Navigation Menu does NavController to changes the current destination?

When we link NavigationView, NavController and NavigationUI in fourth step, this will call [android.view.MenuItem.onNavDestinationSelected] whenever a menu item is selected. If the id of the MenuItem (of the Navigation Menu in the Navigation View) matches the id of the destination (in the Navigation Graph), the NavController can then navigate to that destination automatically.