Drawer Content Implementation in Jetpack Compose: A Complete Guide

Navigation drawers are a crucial UI element in Android apps, offering a convenient way to provide access to key features and sections of the application. Jetpack Compose simplifies the process of creating and managing these drawers. In this blog post, we’ll explore how to implement a navigation drawer using Jetpack Compose.

What is a Navigation Drawer?

A navigation drawer (or simply “drawer”) is a panel that slides in from the left (or right in RTL layouts) and contains the app’s main navigation options. It is typically revealed by swiping from the edge of the screen or by tapping a menu icon in the app bar.

Why Use a Navigation Drawer?

  • Improved User Experience: Consolidates navigation into a single, easily accessible panel.
  • Clean UI: Keeps the main screen uncluttered by hiding secondary functions in the drawer.
  • Consistent Navigation: Provides a consistent and predictable way for users to navigate the app.

How to Implement Drawer Content in Jetpack Compose

To implement a navigation drawer in Jetpack Compose, you’ll use the ModalNavigationDrawer composable, which includes handling drawer state, opening and closing animations, and scrim behavior.

Step 1: Add Dependencies

Ensure you have the necessary dependencies in your build.gradle file:

dependencies {
    implementation("androidx.compose.material3:material3:1.1.1") // or newer
    implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
    implementation("androidx.activity:activity-compose:1.7.2") // for Content Providers

}

Step 2: Basic Implementation of ModalNavigationDrawer

Here’s the basic setup of a navigation drawer with minimal content:


import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val scope = rememberCoroutineScope()

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                Spacer(Modifier.height(12.dp))
                NavigationDrawerItem(
                    label = { Text(text = "Item 1") },
                    selected = false,
                    onClick = {
                        scope.launch {
                            drawerState.close()
                        }
                    },
                    modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                )
                NavigationDrawerItem(
                    label = { Text(text = "Item 2") },
                    selected = false,
                    onClick = {
                        scope.launch {
                            drawerState.close()
                        }
                    },
                    modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                )
            }
        },
        content = {
            Scaffold(
                topBar = {
                    TopAppBar(
                        title = { Text("Drawer Content Example") },
                        navigationIcon = {
                            IconButton(onClick = {
                                scope.launch {
                                    drawerState.open()
                                }
                            }) {
                                Icon(Icons.Filled.Menu, contentDescription = "Menu")
                            }
                        }
                    )
                }
            ) { contentPadding ->
                Box(modifier = Modifier.fillMaxSize().padding(contentPadding), contentAlignment = Alignment.Center) {
                    Text("Content of the Main Screen")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
    MainScreen()
}

In this example:

  • ModalNavigationDrawer is the primary composable that wraps the entire layout, including the drawer and the main content.
  • drawerState is a rememberDrawerState which controls whether the drawer is opened or closed.
  • drawerContent defines the content within the drawer. Here, we use ModalDrawerSheet which hosts NavigationDrawerItem for each option.
  • content is the main content of the screen, using a Scaffold to manage the top bar and content area.

Step 3: Adding Drawer Items

Enhance the drawer content by adding navigation items that respond to user clicks.


import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.sp

data class NavigationItem(
    val title: String,
    val selectedIcon: ImageVector,
    val unselectedIcon: ImageVector,
    val onClick: () -> Unit
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreenWithItems() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val scope = rememberCoroutineScope()
    var selectedItemIndex by rememberSaveable { mutableStateOf(0) }

    val items = listOf(
        NavigationItem(
            title = "Home",
            selectedIcon = Icons.Filled.Home,
            unselectedIcon = Icons.Outlined.Home,
            onClick = { println("Navigating to Home") }
        ),
        NavigationItem(
            title = "Settings",
            selectedIcon = Icons.Filled.Settings,
            unselectedIcon = Icons.Outlined.Settings,
            onClick = { println("Navigating to Settings") }
        ),
        NavigationItem(
            title = "Profile",
            selectedIcon = Icons.Filled.Person,
            unselectedIcon = Icons.Outlined.Person,
            onClick = { println("Navigating to Profile") }
        ),
        NavigationItem(
            title = "Help",
            selectedIcon = Icons.Filled.QuestionMark,
            unselectedIcon = Icons.Outlined.QuestionMark,
            onClick = { println("Navigating to Help") }
        ),
    )

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                Spacer(Modifier.height(12.dp))
                items.forEachIndexed { index, item ->
                    NavigationDrawerItem(
                        label = { Text(item.title, fontSize = 16.sp) },
                        selected = selectedItemIndex == index,
                        onClick = {
                            selectedItemIndex = index
                            item.onClick() // Placeholder for item click
                            scope.launch {
                                drawerState.close()
                            }
                        },
                        icon = {
                            Icon(
                                imageVector = if (index == selectedItemIndex) {
                                    item.selectedIcon
                                } else item.unselectedIcon,
                                contentDescription = item.title
                            )
                        },
                        modifier = Modifier
                            .padding(NavigationDrawerItemDefaults.ItemPadding)
                    )
                }
            }
        },
        content = {
            Scaffold(
                topBar = {
                    TopAppBar(
                        title = { Text("Drawer Content Example") },
                        navigationIcon = {
                            IconButton(onClick = {
                                scope.launch {
                                    drawerState.open()
                                }
                            }) {
                                Icon(Icons.Filled.Menu, contentDescription = "Menu")
                            }
                        }
                    )
                }
            ) { contentPadding ->
                Box(modifier = Modifier.fillMaxSize().padding(contentPadding), contentAlignment = Alignment.Center) {
                    Text("Content of the Main Screen")
                }
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
fun MainScreenWithItemsPreview() {
    MainScreenWithItems()
}

Key enhancements in this example:

  • Data Class for Navigation Items: Using a data class to manage navigation item properties like titles, icons, and click actions.
  • State Management: Managing the selected state of each navigation item using mutableStateOf.
  • Click Actions: Implementing placeholder click actions to demonstrate handling user interactions.

Best Practices for Navigation Drawer Implementation

  • Consistency: Maintain a consistent design language across all drawer items.
  • Accessibility: Ensure your drawer content is accessible to all users by providing proper content descriptions for icons.
  • Responsiveness: Test on various screen sizes to ensure optimal behavior.

Conclusion

Jetpack Compose offers a straightforward and flexible way to implement navigation drawers in Android apps. By using the ModalNavigationDrawer composable along with well-defined drawer items, you can create intuitive and engaging user experiences. Ensure you follow best practices to maximize accessibility and maintain a consistent look and feel across your application.