All You Need To Know About Modal Navigation Drawers In Jetpack Compose
Tags: kotlin, android, androiddev, jetpackcompose
Published: 2023-11-15 13:56:31

---
title: All You Need To Know About Modal Navigation Drawers In Jetpack Compose
published: true
date: 2023-11-15 13:19:25 UTC
tags: kotlin, android, androiddev, jetpackcompose
canonical_url: https://joelsblog.hashnode.dev/all-you-need-to-know-about-modal-navigation-drawers-in-jetpack-compose
---

We've all seen the side menu which provides us with a map of the various screens of an app. From _Gmail_ _to_ _Reddit_, these are lifesavers when it comes to finding various functionalities. For Android developers, this seems like the go to choice for navigating within the app. You'd think there would be a resource revealing how to correctly implement this component. However, that is far from the case. I too was stuck for days trying to figure out how exactly to put this into my app but all I found was solutions that were incomplete or not optimal with a bunch of excess code.

**In this article, I will take you through this process in a step by step manner so you can easily have this feature in your app**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700052876910/17c7eb25-af79-4800-a116-e7b8db8942f9.jpeg)

Before we get started, you'll need a navigation dependency in your `build.gradle` app level file. If it isn't already present, add this `implementation("androidx.navigation:navigation-compose:2.7.5")`  
I have tried my best to not leave out any explanation which has resulted in the article being a tad long. Feel free to skip over the parts that you are well acquainted with.
<br>
### Creating the _Screens_ Class

First, we'll create a sealed class containing the screens accessible from the drawer.

```
sealed class Screens(
    val title: String,
    val route: String,
    val icon: ImageVector,
    val badgeCount: Int? = null
) {
    object Inbox: Screens(
        title = "Inbox",
        route = "inbox",
        icon = Icons.Default.Email,
        badgeCount = 31
    )
    object Sent: Screens(
        title = "Sent",
        route = "sent",
        icon = Icons.Default.Send
    )
    object Starred: Screens(
        title = "Starred",
        route = "starred",
        icon = Icons.Default.Star,
        badgeCount = 15
    )
    object Spam: Screens(
        title = "Spam",
        route = "spam",
        icon = Icons.Default.Warning
    )
    object Bin: Screens(
        title = "Bin",
        route = "bin",
        icon = Icons.Default.Delete
    )
}

```

We have four parameters in the _Screens_ class,  
1. _title_: The text that appears on the top bar of each individual screen  
2. _route_: The route used while navigating to a screen  
3. _icon_: The icon that appears at the side of each item in the navigation drawer  
4. _badgeCount_: The text that will appear on a badge if needed. This is made nullable as not every item will need this
<br>
### Creating a NavHost to Switch Between Screens

We now define the navigation of our app.

```
@Composable
fun DrawerNavigation(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = Screens.Inbox.route
    ) {
        composable(Screens.Inbox.route) {
            InboxScreen()
        }
        composable(Screens.Sent.route) {
            SentScreen()
        }
        composable(Screens.Starred.route) {
            StarredScreen()
        }
        composable(Screens.Spam.route) {
            SpamScreen()
        }
        composable(Screens.Bin.route) {
            BinScreen()
        }
    }
}

```

This is a fairly straightforward step. A _NavHost_ is created with the start destination as **Inbox.** Functions are provided to navigate to each screen. If you are not familiar with this, please refer to [Navigation in Jetpack Compose](https://developer.android.com/jetpack/androidx/releases/navigation)
<br>
### Defining the variables needed in our Composable

```
val navController = rememberNavController()
val currentBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry?.destination?.route ?: Screens.Inbox
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val screens = listOf(
    Screens.Inbox,
    Screens.Sent,
    Screens.Starred,
    Screens.Spam,
    Screens.Bin
)

```

First, we have a `navController` that will be used to navigate to a screen when its item is clicked in the drawer. `currentBackStackEntry` returns the latest back stack entry. When the navController goes to another screen, the back stack is altered, the function gets recomposed and returns the current back stack entry. `currentRoute` stores the unique route of the latest entry onto the back stack. If it is null, it stores `Screens.Inbox` . `drawerState` helps us to store the current state of the drawer (open or close) while `coroutineScope` provides us with a way to call the open and close suspend functions. `screens` simply stores the list of screens that can be accessed from the drawer.
<br>
### Creating a Drawer Header (optional)

You may skip this step if your navigation drawer does not need a header. I have created a very basic design. I encourage you to get as creative as possible.

```
@Composable
fun DrawerHeader() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(top = 20.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceEvenly
    ) {
        Image(
            painter = painterResource(id = R.drawable.jetpack_compose_icon),
            modifier = Modifier.size(100.dp),
            contentDescription = "App icon"
        )
        Text(
            text = "Nav\nDrawer",
            style = MaterialTheme.typography.headlineMedium,
            fontWeight = FontWeight.Bold,
            textAlign = TextAlign.Center,
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
    }
}

```
<br><br>
### Creating the _ModalNavigationDrawer_

Finally, its time to actually implement it.

```
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavDrawer() {
    val navController = rememberNavController()
    val currentBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = currentBackStackEntry?.destination?.route ?: Screens.Inbox
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val coroutineScope = rememberCoroutineScope()

    val screens = listOf(
        Screens.Inbox,
        Screens.Sent,
        Screens.Starred,
        Screens.Spam,
        Screens.Bin
    )

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                DrawerHeader()
                Divider(thickness = 1.dp, modifier = Modifier.padding(bottom = 20.dp))

                screens.forEach { screen ->
                    NavigationDrawerItem(
                        label = { Text(text = screen.title) },
                        icon = {
                               Icon(imageVector = screen.icon, contentDescription = "${screen.title} icon")
                        },
                        selected = currentRoute == screen.route,
                        modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
                        badge = {
                            screen.badgeCount?.let {
                                Badge(
                                    modifier = Modifier.size(30.dp),
                                    containerColor = MaterialTheme.colorScheme.primaryContainer
                                ) {
                                    Text(text = screen.badgeCount.toString())
                                }
                            }
                        },
                        onClick = {
                            navController.navigate(screen.route) {
                                launchSingleTop = true
                            }
                            coroutineScope.launch {
                                drawerState.close()
                            }
                        }
                    )
                }
            }
        }
    ) {
        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(text = currentRoute.toString().replaceFirstChar { it.uppercase() }) },
                    modifier = Modifier.fillMaxWidth(),
                    colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primaryContainer),
                    navigationIcon = {
                        IconButton(
                            onClick = {
                                coroutineScope.launch { drawerState.open() }
                            }
                        ) {
                            Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu icon")
                        }
                    }
                )
            }
        ) {
            Surface(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(top = it.calculateTopPadding())
            ) {
                DrawerNavigation(navController)
            }
        }
    }
}

```

First of all, we pass `drawerState` to store whether our drawer is open or closed. In `drawerContent` we create a _ModalDrawerSheet_. This is the sheet that appears when the drawer is opened. After calling _DrawerHeader,_ a _NavigationDrawerItem_ is created for each screen. Most of the parameters passed are quite self-explanatory. If `badgeCount` is present in the screen, a badge is created displaying its value. The main talking point however, is the _onClick_ lambda. Anytime an item is clicked we navigate to the respective screen. We specify that `launchSingleTop` is true which ensures that only one copy of our screen can be present at the top of the back stack, i.e. if an item is pressed twice or when its screen is already on top of the back stack, another copy will not be pushed onto the back stack. Besides, we call the _drawerState.close() function from our coroutineScope._

Inside the content, a _Scaffold_ is created where the `topBar` contains the screen title and a menu icon, which when clicked, opens the drawer by calling the _drawerState.open()_ function _from our coroutineScope._

Inside the Scaffold content, _DrawerNavigation_ is called and the navController is passed to handle the _onClick_ events of the navigation items.

Well, that was it. I have written this article based on how much I understand about this topic. Like all other code, even this can be further optimized. Any suggestion or corrections would be greatly appreciated.  
**Hope this helped you in some way.**

Check out the source code on [GitHub](https://github.com/joeldotdias/ModalNavigationDrawer_Implementation.git)  
You can reach out to me on [Twitter](https://twitter.com/joeldotdias) or any other socials on my profile.