How to create a Navigation Drawer with Jetpack Compose

Adding libraries

Go to your project-level gradle.build file, and add the following extension:

buildscript { ext { compose_version = '1.0.5' } // ... }
Code language: Kotlin (kotlin)

Now, go to your app-level gradle.build file, and add the following:

android { // ... kotlinOptions { jvmTarget = '1.8' useIR = true } buildFeatures { // ... compose true } composeOptions { kotlinCompilerExtensionVersion compose_version } } dependencies { // ... implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" implementation "androidx.compose.ui:ui-tooling:$compose_version" implementation 'androidx.activity:activity-compose:1.4.0' implementation "androidx.navigation:navigation-compose:2.4.0-beta02" // ... }
Code language: Kotlin (kotlin)

Creating the MainScreen

In the MainActivity.kt, we’re starting by creating the MainScreen() a composable function that includes the scaffoldState to remember if the drawer is closed or not, the CoroutineScope to be able to open/close the drawer, the NavController to navigate to views(Screens) when you press an item from the drawer and the Scaffold layout to put everything together (top bar, drawer, views)

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MainScreen() } } } @Composable fun MainScreen() { val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) val scope = rememberCoroutineScope() val navController = rememberNavController() // If you want the drawer from the right side, uncomment the following // CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { Scaffold( scaffoldState = scaffoldState, topBar = { /* Add code later */ }, drawerBackgroundColor = colorResource(id = R.color.colorPrimary), // scrimColor = Color.Red, // Color for the fade background when you open/close the drawer drawerContent = { /* Add code later */ }, ) { /* Add code later */ } // } } @Preview(showBackground = true) @Composable fun MainScreenPreview() { MainScreen() }
Code language: Kotlin (kotlin)

Creating the Top Bar

Next, we create a new composable function named TopBar and we pass the CoroutineScope and the ScaffoldState for the menu button(navigationIcon)

@Composable fun TopBar(scope: CoroutineScope, scaffoldState: ScaffoldState) { TopAppBar( title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) }, navigationIcon = { IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { Icon(Icons.Filled.Menu, "") } }, backgroundColor = colorResource(id = R.color.colorPrimary), contentColor = Color.White ) } @Preview(showBackground = false) @Composable fun TopBarPreview() { val scope = rememberCoroutineScope() val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) TopBar(scope = scope, scaffoldState = scaffoldState) }
Code language: Kotlin (kotlin)

Creating the Navigation Drawer

First, we going to create the menu items.

We go to your project name folder and we right-click, select New, and then Kotlin Class/File.

In the new window, we select Sealed Class and we name it NavDrawerItem

This class has 3 parameters:

  • route: A string as the name of the view we’re going to navigate
  • icon: The icon of the menu item
  • title: The title of the menu item

And inside this sealed class we create our six menu items as objects, and we setting the parameters.

sealed class NavDrawerItem(var route: String, var icon: Int, var title: String) { object Home : NavDrawerItem("home", R.drawable.ic_home, "Home") object Music : NavDrawerItem("music", R.drawable.ic_music, "Music") object Movies : NavDrawerItem("movies", R.drawable.ic_movie, "Movies") object Books : NavDrawerItem("books", R.drawable.ic_book, "Books") object Profile : NavDrawerItem("profile", R.drawable.ic_profile, "Profile") object Settings : NavDrawerItem("settings", R.drawable.ic_settings, "Settings") }
Code language: Kotlin (kotlin)

After we created our menu items, it’s time to make the navigation drawer.

Back to MainActivity.kt we create another composable function with the name Drawer and we pass the same parameters as we did before for the TopBar, plus the NavController.

In the beginning, we create a list of all the menu items we want to have in our drawer.

Then we create a Column and inside this column, we have an Image that we use as a header, a forEach loop for the items in the list we created before, and at the bottom a Text as a footer.

@Composable fun Drawer(scope: CoroutineScope, scaffoldState: ScaffoldState, navController: NavController) { val items = listOf( NavDrawerItem.Home, NavDrawerItem.Music, NavDrawerItem.Movies, NavDrawerItem.Books, NavDrawerItem.Profile, NavDrawerItem.Settings ) Column( modifier = Modifier .background(colorResource(id = R.color.colorPrimary)) ) { // Header Image( painter = painterResource(id = R.drawable.logo), contentDescription = R.drawable.logo.toString(), modifier = Modifier .height(100.dp) .fillMaxWidth() .padding(10.dp) ) // Space between Spacer( modifier = Modifier .fillMaxWidth() .height(5.dp) ) // List of navigation items val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route items.forEach { item -> /* Add code later */ } Spacer(modifier = Modifier.weight(1f)) Text( text = "Developed by John Codeos", color = Color.White, textAlign = TextAlign.Center, fontWeight = FontWeight.Bold, modifier = Modifier .padding(12.dp) .align(Alignment.CenterHorizontally) ) } } @Preview(showBackground = true) @Composable fun DrawerPreview() { val scope = rememberCoroutineScope() val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) val navController = rememberNavController() Drawer(scope = scope, scaffoldState = scaffoldState, navController = navController) }
Code language: Kotlin (kotlin)

You won’t be able to see any menu items in the drawer yet because we have to create the DrawerItem view first.

We create the DrawerItem composable function with three parameters:

  • item: The model of each menu item (title, icon)
  • selected: Boolean that allows us to set the selected menu item in a different background (highlighted)
  • onItemClick: A click listener to detect when we tap an item
@Composable fun DrawerItem(item: NavDrawerItem, selected: Boolean, onItemClick: (NavDrawerItem) -> Unit) { val background = if (selected) R.color.colorPrimaryDark else android.R.color.transparent Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .clickable(onClick = { onItemClick(item) }) .height(45.dp) .background(colorResource(id = background)) .padding(start = 10.dp) ) { Image( painter = painterResource(id = item.icon), contentDescription = item.title, colorFilter = ColorFilter.tint(Color.White), contentScale = ContentScale.Fit, modifier = Modifier .height(35.dp) .width(35.dp) ) Spacer(modifier = Modifier.width(7.dp)) Text( text = item.title, fontSize = 18.sp, color = Color.White ) } } @Preview(showBackground = false) @Composable fun DrawerItemPreview() { DrawerItem(item = NavDrawerItem.Home, selected = false, onItemClick = {}) }
Code language: Kotlin (kotlin)

After we created the DrawerItem, we’re going back to the Drawer function.

Inside the forEach loop, we pass the arguments to the DrawerItem and we setting the click listener (onItemClick)

items.forEach { item -> DrawerItem(item = item, selected = currentRoute == item.route, onItemClick = { /* Add code later */ }) }
Code language: Swift (swift)

Creating the Navigation

Now, let’s make each menu item in the drawer to ‘open’ a View(Screen) every time we press them.

We create a new Kotlin file with the name ContentScreen and we create six simple screens that represent each menu item, HomeScreen() for the Home menu item, MusicScreen() for the Music item e.t.c

@Composable fun HomeScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Home View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun HomeScreenPreview() { HomeScreen() } @Composable fun MusicScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Music View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun MusicScreenPreview() { MusicScreen() } @Composable fun MoviesScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Movies View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun MoviesScreenPreview() { MoviesScreen() } @Composable fun BooksScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Books View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun BooksScreenPreview() { BooksScreen() } @Composable fun ProfileScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Profile View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun ProfileScreenPreview() { ProfileScreen() } @Composable fun SettingsScreen() { Column( modifier = Modifier .fillMaxSize() .background(colorResource(id = R.color.colorPrimaryDark)) .wrapContentSize(Alignment.Center) ) { Text( text = "Settings View", fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center, fontSize = 25.sp ) } } @Preview(showBackground = true) @Composable fun SettingsScreenPreview() { SettingsScreen() }
Code language: Kotlin (kotlin)

Back to MainActivity.kt we create the Navigation function with one parameter, the NavHostController.

We setting the HomeScreen() as a startDestination using the route, and the rest screens we created before.

@Composable fun Navigation(navController: NavHostController) { NavHost(navController, startDestination = NavDrawerItem.Home.route) { composable(NavDrawerItem.Home.route) { HomeScreen() } composable(NavDrawerItem.Music.route) { MusicScreen() } composable(NavDrawerItem.Movies.route) { MoviesScreen() } composable(NavDrawerItem.Books.route) { BooksScreen() } composable(NavDrawerItem.Profile.route) { ProfileScreen() } composable(NavDrawerItem.Settings.route) { SettingsScreen() } } }
Code language: Kotlin (kotlin)

Now, again, we’re going back to the Drawer function and inside the DrawerItem we setting the navigation, and at the end, we close the drawer.

items.forEach { item -> DrawerItem(item = item, selected = currentRoute == item.route, onItemClick = { navController.navigate(item.route) { // Pop up to the start destination of the graph to // avoid building up a large stack of destinations // on the back stack as users select items navController.graph.startDestinationRoute?.let { route -> popUpTo(route) { saveState = true } } // Avoid multiple copies of the same destination when // reselecting the same item launchSingleTop = true // Restore state when reselecting a previously selected item restoreState = true } // Close drawer scope.launch { scaffoldState.drawerState.close() } }) }
Code language: Swift (swift)

Putting Them Together

Lastly, we put everything we created before (TopBar, Drawer, Navigation), together into the Scaffold layout in the MainScreen(), and we’re done!

Scaffold( scaffoldState = scaffoldState, topBar = { TopBar(scope = scope, scaffoldState = scaffoldState) }, drawerBackgroundColor = colorResource(id = R.color.colorPrimary), drawerContent = { Drawer(scope = scope, scaffoldState = scaffoldState, navController = navController) }, ) { Navigation(navController = navController) }
You can find the final project here

If you have any questionsplease feel free to leave a comment below

Subscribe
Notify of
guest
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Get once a week my latest tutorials right in your inbox

Check your inbox to confirm your email