Navigation Drawer (Side Menu)
What is a navigation drawer?
A navigation drawer is the side menu that slides in from the left when you tap ☰ or swipe from the edge. It shows a header (user photo and name) and a list of destinations.
The building blocks
Step 1: activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- DrawerLayout = the side menu slides over the main content -->
<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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<!-- 1st child: main content -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Screens (fragments) are swapped here by the Navigation Component -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="96dp"
android:contentDescription="@string/add_note"
app:srcCompat="@drawable/ic_add" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- 2nd child: the drawer itself -->
<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/drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>Step 2: Drawer header
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="176dp"
android:background="@color/brand"
android:gravity="bottom"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/bg_circle_white"
android:contentDescription="@string/app_name"
android:padding="12dp"
android:src="@drawable/ic_note"
app:tint="@color/brand" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/app_name"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tagline"
android:textColor="#E0FFFFFF" />
</LinearLayout>Step 3: Drawer menu
A <group android:checkableBehavior="single"> highlights the current screen. A sub-menu with a title creates the "More" section. Destination items reuse nav graph ids; action_share is a normal action.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/notesFragment" android:icon="@drawable/ic_note" android:title="@string/menu_notes" />
<item android:id="@+id/favoritesFragment" android:icon="@drawable/ic_star" android:title="@string/menu_favourites" />
<item android:id="@+id/subjectsFragment" android:icon="@drawable/ic_subject" android:title="@string/menu_subjects" />
<item android:id="@+id/statsFragment" android:icon="@drawable/ic_stats" android:title="@string/menu_stats" />
</group>
<item android:title="@string/menu_more">
<menu>
<item android:id="@+id/settingsFragment" android:icon="@drawable/ic_settings" android:title="@string/menu_settings" />
<item android:id="@+id/helpFragment" android:icon="@drawable/ic_help" android:title="@string/menu_help" />
<item android:id="@+id/action_share" android:icon="@drawable/ic_share" android:title="@string/share_app" />
</menu>
</item>
</menu>Step 4: MainActivity
package com.codingeasily.studentdiary
import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.codingeasily.studentdiary.data.SettingsStore
import com.codingeasily.studentdiary.databinding.ActivityMainBinding
import com.codingeasily.studentdiary.databinding.NavHeaderBinding
import com.codingeasily.studentdiary.reminder.NotificationHelper
import com.google.android.material.snackbar.Snackbar
/**
* The only Activity. It hosts: Toolbar + Navigation Drawer + Bottom Navigation + FAB
* and a NavHostFragment that swaps the screens (fragments).
*/
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
// Android 13+: ask for notification permission (result comes back here)
private val notificationPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (!granted) {
Snackbar.make(binding.root, R.string.notifications_off, Snackbar.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
navController = navHost.navController
// Top-level screens show the ☰ (drawer) icon; other screens show ←
val topLevel = setOf(
R.id.notesFragment, R.id.subjectsFragment, R.id.statsFragment,
R.id.favoritesFragment, R.id.settingsFragment, R.id.helpFragment
)
appBarConfiguration = AppBarConfiguration(topLevel, binding.drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.bottomNav.setupWithNavController(navController)
binding.navView.setupWithNavController(navController)
// Drawer has one extra item ("Share app") that is not a screen
binding.navView.setNavigationItemSelectedListener { item ->
if (item.itemId == R.id.action_share) shareApp()
else NavigationUI.onNavDestinationSelected(item, navController)
binding.drawerLayout.closeDrawer(GravityCompat.START)
true
}
// Show bottom bar only on the 3 main tabs, FAB only on the notes list
val bottomScreens = setOf(R.id.notesFragment, R.id.subjectsFragment, R.id.statsFragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.bottomNav.isVisible = destination.id in bottomScreens
if (destination.id == R.id.notesFragment) binding.fab.show() else binding.fab.hide()
}
binding.fab.setOnClickListener { navController.navigate(R.id.action_global_editor) }
// Close the drawer with the back button before leaving the screen
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
}
})
askNotificationPermission()
}
override fun onResume() {
super.onResume()
// The name can change in Settings, so refresh the drawer header every time
val header = NavHeaderBinding.bind(binding.navView.getHeaderView(0))
header.tvUserName.text = getString(R.string.hello_user, SettingsStore(this).userName)
}
override fun onSupportNavigateUp(): Boolean =
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
private fun askNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !NotificationHelper.canNotify(this)) {
notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
private fun shareApp() {
val send = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text))
}
startActivity(Intent.createChooser(send, getString(R.string.share_app)))
}
}How a drawer click works
Back button closes the drawer
The OnBackPressedCallback in MainActivity checks isDrawerOpen(GravityCompat.START): if the drawer is open, Back closes it; otherwise the callback disables itself for a moment and lets the NavHost go back.
Common mistakes
- NavigationView placed first inside DrawerLayout → it covers the content and cannot close.
- Missing
android:layout_gravity="start"on NavigationView → crash "No drawer view found". - Theme with an ActionBar plus your Toolbar → two bars; use a NoActionBar theme.
- Calling
setNavigationItemSelectedListenerwithout callingonNavDestinationSelected→ menu clicks stop navigating.
Summary
- DrawerLayout(content, NavigationView) + header layout + menu XML.
AppBarConfiguration(topLevelIds, drawerLayout)gives the ☰ icon;navView.setupWithNavControllerlinks items.- Handle extra items yourself, close the drawer, and close it on Back.
Add a "Rate us" item to the drawer that opens the Play Store page with an implicit intent, and show the user's email under the name in the header.
