Navigation Drawer APIs in Material 3 Jetpack Compose

Jetpack Compose Material 3 Navigation Drawer

In this article, we’ll learn how to implement the navigation drawer APIs in material 3 Jetpack Compose.

Prerequisites:

What is a Navigation Drawer in Android?

It is nothing but a menu that slides in from the left. It shows different screens (destinations) to the user. We can also open it by swiping from the left.

Navigation Drawer Example

Material 3 Jetpack Compose provides three types of Navigation Drawers:

  1. Modal Navigation Drawer
  2. Dismissible Navigation Drawer
  3. Permanent Navigation Drawer

Let’s see how to implement them in the Android Studio.

First, create an empty Compose project and open MainActivity. Create a MyUI() composable and call it from the onCreate() method. We’ll write our code in it.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.material3.Button
import androidx.compose.material3.DismissibleDrawerSheet
import androidx.compose.material3.DismissibleNavigationDrawer
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourProjectNameTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Column(
                        modifier = Modifier.fillMaxSize()
                    ) {
                        MyUI()
                    }
                }
            }
        }
    }
}


@Composable
fun MyUI() {

}

When it is opened, users cannot interact with the rest of the app’s content. It shows a semi-transparent overlay (scrim) on the content.

Navigation Drawer Example

Jetpack Compose provides ModalNavigationDrawer() method:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModalNavigationDrawer(
    drawerContent: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
    gesturesEnabled: Boolean = true,
    scrimColor: Color = DrawerDefaults.scrimColor,
    content: @Composable () -> Unit
)

drawerContent – Content inside this drawer.

modifier – The Modifier to be applied to this drawer.

drawerState – To manage the state of the drawer.

gesturesEnabled – Whether or not the drawer can be interacted with by gestures (swiping from left).

scrimColor – The semi-transparent overlay that obscures content when the drawer is open.

content – The content of the rest of the UI.

Example:

@Composable
fun MyUI() {
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val coroutineScope = rememberCoroutineScope()

    val drawerItemList = prepareNavigationDrawerItems()
    val selectedItem = remember { mutableStateOf(drawerItemList[0]) }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                // add drawer content here
                // this is a column scope
                // so, if you add multiple elements, they are placed vertically
                Spacer(Modifier.height(12.dp))
                drawerItemList.forEach { item ->
                    NavigationDrawerItem(
                        icon = { Icon(imageVector = item.icon, contentDescription = null) },
                        label = { Text(text = item.label) },
                        selected = (item == selectedItem.value),
                        onClick = {
                            coroutineScope.launch {
                                drawerState.close()
                            }
                            selectedItem.value = item
                        },
                        modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                    )
                }
            }
        }
    ) {
        // app content
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = if (drawerState.isClosed) "Drawer is Closed" else "Drawer is Opened")
            Button(
                onClick = {
                    coroutineScope.launch {
                        drawerState.open()
                    }
                }
            ) {
                Text("Click to open")
            }
        }
    }
}

private fun prepareNavigationDrawerItems(): List<NavigationDrawerData> {
    val drawerItemsList = arrayListOf<NavigationDrawerData>()

    // add items
    drawerItemsList.add(NavigationDrawerData(label = "Home", icon = Icons.Filled.Home))
    drawerItemsList.add(NavigationDrawerData(label = "Profile", icon = Icons.Filled.Person))
    drawerItemsList.add(NavigationDrawerData(label = "Cart", icon = Icons.Filled.ShoppingCart))
    drawerItemsList.add(NavigationDrawerData(label = "Settings", icon = Icons.Filled.Settings))

    return drawerItemsList
}

data class NavigationDrawerData(val label: String, val icon: ImageVector)

Output:

Navigation Drawer Example

The NavigationDrawerData class is used to hold the drawer item’s name and icon. Inside the prepareNavigationDrawerItems(), we are creating the list and adding the drawer items.

In the MyUI(), we added the navigation drawer. First, we created a state variable using the rememberDrawerState() method.

@Composable
fun rememberDrawerState(
    initialValue: DrawerValue,
    confirmStateChange: (DrawerValue) -> Boolean = { true }
): DrawerState

initialValue – The initial value of the state. There are two values: DrawerValue.Closed and DrawerValue.Open

confirmStateChange – Optional callback invoked to confirm or veto a pending state change.

Next, we added ModalNavigationDrawer composable. To add the items, we are using the drawerContent parameter. First, we added the ModalDrawerSheet and within the sheet, we displayed the items using the NavigationDrawerItem() method. The APIs look like this:

Modal Drawer Sheet:

@Composable
fun ModalDrawerSheet(
    modifier: Modifier = Modifier,
    drawerShape: Shape = DrawerDefaults.shape,
    drawerContainerColor: Color = DrawerDefaults.containerColor,
    drawerContentColor: Color = contentColorFor(drawerContainerColor),
    drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
    windowInsets: WindowInsets = DrawerDefaults.windowInsets,
    content: @Composable ColumnScope.() -> Unit
)

modifier – The Modifier to be applied to this drawer’s content.

drawerShape – The shape of the drawer’s container.

drawerContainerColor – The background color. Use Color.Transparent to have no color.

drawerContentColor – The preferred color for the content.

drawerTonalElevation – Elevation of the drawer.

windowInsets – A window insets for the sheet.

content – Drawer items.

Navigation Drawer Item:

@Composable
fun NavigationDrawerItem(
    label: @Composable () -> Unit,
    selected: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    icon: (@Composable () -> Unit)? = null,
    badge: (@Composable () -> Unit)? = null,
    shape: Shape = NavigationDrawerTokens.ActiveIndicatorShape.value,
    colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)

labelText label for this item.

selected – Whether this item is selected.

onClick – It is called when this item is clicked.

modifier – The Modifier to be applied to this item.

icon – Optional icon for this item, typically an Icon.

badge – Optional badge to show on this item from the end side.

colors – Colors of this item.

interactionSource – It is to observe and customize the interactions. For example, we can disable the ripple effect.

2. Dismissible Navigation Drawer:

It is similar to the modal navigation drawer, but when it is opened, it covers the whole screen.

Dismissible Navigation Drawer

Example:

@Composable
fun MyUI() {
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val coroutineScope = rememberCoroutineScope()

    val drawerItemList = prepareNavigationDrawerItems()
    val selectedItem = remember { mutableStateOf(drawerItemList[0]) }

    DismissibleNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            DismissibleDrawerSheet {
                // add drawer content here
                // this is a column scope
                // so, if you add multiple elements, they are placed vertically
                Spacer(Modifier.height(12.dp))
                drawerItemList.forEach { item ->
                    NavigationDrawerItem(
                        icon = { Icon(imageVector = item.icon, contentDescription = null) },
                        label = { Text(text = item.label) },
                        selected = (item == selectedItem.value),
                        onClick = {
                            coroutineScope.launch {
                                drawerState.close()
                            }
                            selectedItem.value = item
                        },
                        modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                    )
                }
            }
        }
    ) {
        // app content
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = if (drawerState.isClosed) "Drawer is Closed" else "Drawer is Opened")
            Button(
                onClick = {
                    coroutineScope.launch {
                        drawerState.open()
                    }
                }
            ) {
                Text("Click to open")
            }
        }
    }
}

private fun prepareNavigationDrawerItems(): List<NavigationDrawerData> {
    val drawerItemsList = arrayListOf<NavigationDrawerData>()

    // add items
    drawerItemsList.add(NavigationDrawerData(label = "Home", icon = Icons.Filled.Home))
    drawerItemsList.add(NavigationDrawerData(label = "Profile", icon = Icons.Filled.Person))
    drawerItemsList.add(NavigationDrawerData(label = "Cart", icon = Icons.Filled.ShoppingCart))
    drawerItemsList.add(NavigationDrawerData(label = "Settings", icon = Icons.Filled.Settings))

    return drawerItemsList
}

data class NavigationDrawerData(val label: String, val icon: ImageVector)

Output:

Dismissible Navigation Drawer

The code is similar to the Modal Navigation Drawer. We replaced ModalNavigationDrawer() with DismissibleNavigationDrawer(), and ModalDrawerSheet with DismissibleDrawerSheet.

3. Permanent Navigation Drawer:

It is always visible and usually used for frequently switching destinations. We generally, don’t use it for mobile screens. Follow this guide for more information.

This is all about navigation drawer APIs in Material 3 Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.

Related Articles:


References:

Leave a Comment