Crafting an Elegant Navigation Drawer in Jetpack Compose

Abhishek Pathak
3 min readJul 29, 2024

Introduction

Imagine navigating through your app with a sleek, slide-in menu that seamlessly directs you to various sections. This tutorial will guide you through the creation of an elegant navigation drawer in Jetpack Compose, featuring dynamic icons that adapt to the user’s selection.

Step 1: Designing the Data Class for Navigation Items

Our first step is to define a data class that represents the navigation items. This class will handle both selected and unselected states of the items.

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.ui.graphics.vector.ImageVector

data class NavigationItems(
val title: String,
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector,
val badgeCount: Int? = null
)

Step 2: Curating a List of Navigation Items

Next, we create a curated list of items that will be displayed in our navigation drawer. Each item will have a title, as well as distinct icons for selected and unselected states.

val items = listOf(
NavigationItems(
title = "Home",
selectedIcon = Icons.Filled.Home,
unselectedIcon = Icons.Outlined.Home
),
NavigationItems(
title = "Profile",
selectedIcon = Icons.Filled.Person,
unselectedIcon = Icons.Outlined.Person
),
NavigationItems(
title = "Settings",
selectedIcon = Icons.Filled.Settings,
unselectedIcon = Icons.Outlined.Settings
),
NavigationItems(
title = "Logout",
selectedIcon = Icons.Filled.ExitToApp,
unselectedIcon = Icons.Outlined.ExitToApp
)
)

Step 3: Set Up State and DrawerState

To keep track of the selected item and the drawer’s open/closed state, we set up necessary state management.

var selectedItemIndex by rememberSaveable { mutableStateOf(0) }
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

Step 4: Create the Modal Navigation Drawer

Now, let’s bring everything together in a composable function that represents our navigation drawer.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavigationDrawer() {
val items = listOf(
NavigationItems(
title = "Home",
selectedIcon = Icons.Filled.Home,
unselectedIcon = Icons.Outlined.Home
),
NavigationItems(
title = "Profile",
selectedIcon = Icons.Filled.Person,
unselectedIcon = Icons.Outlined.Person
),
NavigationItems(
title = "Settings",
selectedIcon = Icons.Filled.Settings,
unselectedIcon = Icons.Outlined.Settings
),
NavigationItems(
title = "Logout",
selectedIcon = Icons.Filled.ExitToApp,
unselectedIcon = Icons.Outlined.ExitToApp
)
)

var selectedItemIndex by rememberSaveable { mutableStateOf(0) }
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(modifier = Modifier.height(16.dp))
items.forEachIndexed { index, item ->
NavigationDrawerItem(
label = { Text(text = item.title) },
selected = index == selectedItemIndex,
onClick = {
selectedItemIndex = index
scope.launch { drawerState.close() }
},
icon = {
Icon(
imageVector = if (index == selectedItemIndex) item.selectedIcon else item.unselectedIcon,
contentDescription = item.title
)
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
},
gesturesEnabled = true
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "Navigation Drawer Example") },
navigationIcon = {
IconButton(onClick = {
scope.launch {
drawerState.apply { if (isClosed) open() else close() }
}
}) {
Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) {
Box(modifier = Modifier.padding(it)) {
when (selectedItemIndex) {
0 -> HomeScreen()
1 -> ProfileScreen()
2 -> SettingsScreen()
3 -> LogoutScreen()
}
}
}
}
}

Step 5: Create Screen Composables

Create separate composables for the different screens that users will navigate to.

@Composable
fun HomeScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
Text("Home Screen", style = MaterialTheme.typography.h4)
}
}

@Composable
fun ProfileScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
Text("Profile Screen", style = MaterialTheme.typography.h4)
}
}

@Composable
fun SettingsScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
Text("Settings Screen", style = MaterialTheme.typography.h4)
}
}

@Composable
fun LogoutScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
Text("Logout Screen", style = MaterialTheme.typography.h4)
}
}

Step 6: Set Content in Activity

Finally, set the content of your main activity to the NavigationDrawer composable.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
NavigationDrawer()
}
}
}
}

Conclusion

With Jetpack Compose, building an elegant navigation drawer is both intuitive and efficient. By leveraging the power of composables, state management, and dynamic icons, you can create a seamless and visually appealing navigation experience for your users.

Happy coding! :)

Thank you for reading my article. I really appreciate your response.

Clap if this article helps you. If I got something wrong, please comment for improve.
let’s connect on
Linkedin , GitHub

--

--