Navigation drawer in Android Jetpack Compose (Material Design 3)
In the fast-paced world of Android development, staying abreast of the latest technologies is paramount to creating modern, intuitive, and visually stunning applications. One design element that plays a pivotal role in enhancing user experience is the Navigation Drawer. With the advent of Material Design 3 and the power of Jetpack Compose, implementing a sophisticated Navigation Drawer has become not only essential but also more accessible than ever.
Unveiling the Navigation Drawer
The Navigation Drawer is more than just a UI element; it’s a critical component that contributes to the overall user experience. Here are some key reasons why the Navigation Drawer holds such significance in Android applications:
1. Intuitive Navigation:
Users expect a seamless and intuitive way to navigate through different sections of an app. The Navigation Drawer provides a standardized mechanism for accessing various screens with minimal effort.
2. Consistency Across Screens:
The drawer’s persistent presence ensures consistency in navigation patterns across different screens, contributing to a cohesive and user-friendly app experience.
3. Optimal Use of Screen Real Estate:
With the proliferation of diverse screen sizes and aspect ratios, the Navigation Drawer allows efficient utilization of screen real estate. It provides a space-saving solution while accommodating a growing number of app features.
4. Enhanced Discoverability:
By presenting a concise list of app sections or features, the Navigation Drawer enhances discoverability, helping users quickly locate and access the content they seek.
In the upcoming sections, we’ll dive into the implementation details of a Navigation Drawer using Jetpack Compose and Material Design 3, exploring the provided code and unraveling the intricacies of crafting a sleek and functional navigation experience for your Android application. Get ready to elevate your app’s navigation game to new heights!
Overview of the Navigation Drawer Implementation
The key components are:
1. build.gradle.kts (:app)
:
We will add two dependencies here, one for Material design 3 and second for navigation as we are going to use navigation component to navigate between two screens.
2. ActiveScreen.kt
:
This file defines a sealed class ActiveScreen
that encapsulates information about different screens in the app. Two screens, "Home" and "About," are represented as objects with properties like icon, content description, title, and route. This structured approach allows for easy management and extension of screens.
3. MainScreen
:
The MainScreen
composable is the heart of the app. Here's an overview of its features:
- Navigation Setup: It initializes a
NavController
and aDrawerState
to manage the navigation and drawer state, respectively. - Drawer Content: The content of the drawer is defined using Jetpack Compose components. It includes a title/header and a list of navigation items (
NavigationDrawerItem
). Icons, labels, and navigation actions are dynamically generated based on thenavDrawerItem
list. - Navigation Actions: When a drawer item is clicked, it triggers navigation actions. The
NavController
is used to navigate to the specified route. Additionally, the code includes considerations for handling navigation back stack and ensuring a smooth user experience.
4. DrawerNavGraph.kt
:
This file sets up the navigation graph for the app. It defines two destinations, “Home” and “About,” associating each with its respective composable (HomeScreen()
and AboutScreen()
). The starting destination is set to "Home."
4. CenterAlignedToolbar.kt
:
This file introduces a CenterAlignedToolbar
composable, providing a clean and centralized toolbar with navigation menu functionality.
So after understanding overview of code and flow of code, let’s dive into actual code now!!
Beneath the Code Hood: Crafting the Navigation Drawer Magic
Alright, code wizards and keyboard rockstars, buckle up your binary belts because it’s time to breakdance through the actual code! Let’s flip some bits and drop some code bombs! We’ll go through each line of code, and I’ll try my best to explain them in detail.
Starting with build.gradle.kts (:app)
—
implementation("androidx.compose.material3:material3:1.2.0-alpha12")
implementation("androidx.navigation:navigation-compose:2.7.5")
Now that we’ve added the required dependencies to our project, let’s sync the project to make sure everything is up-to-date. Once the sync is complete, we’ll embark on creating a Kotlin sealed class named ActiveScreen
. This class is the backbone of our navigation drawer, holding information about the different screens in our app. Let's dive in and explore the contents of this essential file.
import com.codewithzebru.composedemo.R
sealed class ActiveScreen(
val icon: Int,
val contentDescription: String,
val title: String,
val route: String
) {
object Home: ActiveScreen(
icon = R.drawable.round_home_24,
contentDescription = "Go to home screen",
title = "Home",
route = "home_screen"
)
object About: ActiveScreen(
icon = R.drawable.outline_info_24,
contentDescription = "Go to about screen",
title = "About",
route = "about_screen"
)
}
ActiveScreen
is a sealed class, which means all subclasses must be declared in the same file where the sealed class is declared.- It has four properties:
icon
(resource ID for the screen's icon),contentDescription
(a description of the screen for accessibility purposes),title
(the title or name of the screen), androute
(the route string associated with the screen). - Two objects (
Home
andAbout
) are declared as subclasses ofActiveScreen
. These represent distinct screens in the app. Home
has an icon representing a home, a content description, the title "Home," and the route "home_screen."About
has an icon representing information, a content description, the title "About," and the route "about_screen."
Alright, that’s it for ActiveSreen.kt
. Let’s take a closer look at the MainScreen.kt
, which plays a crucial role in implementing the navigation drawer functionality.
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.launch
@Composable
fun MainScreen() {
val drawerNavController = rememberNavController()
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val navDrawerItem = listOf(
ActiveScreen.Home,
ActiveScreen.About
)
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
modifier = Modifier.width(280.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
// HEADER - TITLE
Text(
text = stringResource(id = R.string.app_name),
fontSize = 18.sp,
fontWeight = FontWeight.W500,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
// NAVIGATION ITEMS
navDrawerItem.forEachIndexed { index, activeScreen ->
NavigationDrawerItem(
icon = {
Image(
painter = painterResource(id = activeScreen.icon),
contentDescription = activeScreen.contentDescription
)
},
label = {
Text(
text = activeScreen.title,
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.W400
)
},
selected = false,
onClick = {
coroutineScope.launch { drawerState.close() }
if (index == 0) {
drawerNavController.navigate(activeScreen.route) {
drawerNavController.graph.startDestinationRoute.let {
popUpTo(it!!) {
saveState = true
}
}
restoreState = true
}
} else if (index == 1) {
drawerNavController.navigate(activeScreen.route) {
drawerNavController.graph.startDestinationRoute.let {
popUpTo(it!!) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
}
) // navigation-drawer-item
}
} // column
} // model-drawer-sheet
} // model-navigation-drawer
) {
Scaffold(
topBar = { CenterAlignedToolbar(drawerState, coroutineScope) }
) {
Box(Modifier.padding(it)) {
DrawerNavGraph(drawerNavController)
}
}
}
}
Feeling a bit overwhelmed? Fear not! Let’s break down this intricate piece of code into bite-sized chunks and unravel its mysteries together. Let’s simplify the complexity step by step.
- The
MainScreen
composable starts by initializing some important variables required for handling the navigation drawer. This includes creating a navigation controller (drawerNavController
), managing the drawer state (drawerState
), and setting up a coroutine scope (coroutineScope
). - The
ModalNavigationDrawer
is a fundamental part of the Material Design 3 setup, providing the modal drawer for navigation. It takes the drawer state and the drawer content as parameters. The drawer content is defined within theModalDrawerSheet
, where you find the header and navigation items. - Inside the
ModalDrawerSheet
, aColumn
is used to arrange the content vertically. It includes a header displaying the app title (you can add image as well) and a list of navigation items. Each item is represented by aNavigationDrawerItem
, containing an icon, label, and anonClick
action to handle navigation. - The
onClick
action inside eachNavigationDrawerItem
handles the navigation when an item is clicked. It closes the drawer, and based on the index of the clicked item, it navigates to the corresponding screen using thedrawerNavController
. Special considerations are made for preserving and restoring the navigation state. - The
Scaffold
composable structures the app's UI with a centered toolbar, a padded content area, and an integrated navigation graph for the drawer. The centered toolbar, driven byCenterAlignedToolbar
, positions content elegantly. Enclosed in a paddedBox
, the navigation graph (DrawerNavGraph
) ensures smooth navigation between drawer destinations. It's the foundation for a cohesive user experience.
We’ve woven the threads of the navigation drawer, complete with its header and enchanting items. But wait, the magic doesn’t end there. Now, it’s time to unveil the secret passages that guide our journey through screens. Let’s dive into the DrawerNavGraph.kt
to connect the dots and discover how navigation is seamlessly orchestrated.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@Composable
fun DrawerNavGraph(drawerNavController: NavHostController) {
NavHost(navController = drawerNavController, startDestination = ActiveScreen.Home.route) {
composable(ActiveScreen.Home.route) {
HomeScreen()
}
composable(ActiveScreen.About.route) {
AboutScreen()
}
}
}
- The
DrawerNavGraph
function takes aNavHostController
as a parameter, which is responsible for handling navigation within the drawer. - The
NavHost
sets up the navigation host for the drawer. It uses the provideddrawerNavController
as the controller and specifies thestartDestination
as the home screen's route from theActiveScreen
sealed class. - Two
composable
functions define the destinations within the navigation host. When the navigation controller reaches the home screen or about screen routes, it composes the correspondingHomeScreen()
orAboutScreen()
composable.
And our CenterAlignedToolbar
—
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DrawerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CenterAlignedToolbar(drawerState: DrawerState, coroutineScope: CoroutineScope) {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(id = R.string.app_name),
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.titleLarge
)
}, // title
navigationIcon = {
Image(
imageVector = Icons.Rounded.Menu,
contentDescription = "Navigation menu",
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary),
modifier = Modifier
.padding(start = 20.dp)
.size(31.dp)
.clickable {
coroutineScope.launch {
drawerState.apply {
if (isClosed) open() else close()
}
}
},
)
}, // navigation-icon
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary
)
) // center-aligned-top-app-bar
}
CenterAlignedTopAppBar
: Composable for creating a top app bar with a centered title and customizable navigation icon.title
Parameter: Displays the app name using theText
composable with specified styling.navigationIcon
Parameter: Utilizes theImage
composable to represent the navigation menu icon, enabling users to toggle the drawer's state with a clickable coroutine.- Navigation Logic: The coroutine inside
clickable
manages the drawer's state, opening it if closed and vice versa. colors
Parameter: Customizes the app bar's colors for a cohesive theme, using theTopAppBarDefaults.centerAlignedTopAppBarColors
function.
Don’t forget to mention our MainScreen()
in MainActivity.kt
.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.codebyzebru.navigationdemo.ui.theme.NavigationDemoTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationDemoTheme {
MainScreen()
}
}
}
}
THAT’S IT!! But…
Are you not familiar with navigation components? No worries mate. I got you. Let me give you brief on, what we have done so far with navigation component any why we used it, before we end this blog here.
A Quick Basics Guide: Navigation Components
1. NavHostController
:
The NavHostController
is like the captain of our navigation ship. It steers the app through different screens or destinations. In simpler terms, it's the one responsible for knowing where the app is and where it should go next.
2. NavHost
:
The NavHost
is the stage where our navigation drama unfolds. It's the container that holds all our scenes (composables). Think of it as the theater where the show happens. It takes the NavHostController
and the starting destination as its parameters.
3. composable
:
Each composable
is like a scene in our play. It represents a screen or a view. In our case, we have two scenes: one for the home screen and another for the about screen. When the NavHostController
reaches a certain point in the navigation, it shows the corresponding composable
—just like switching scenes in a play.
4. ActiveScreen
Sealed Class:
The ActiveScreen
sealed class is our script. It defines the different screens (Home and About) with their properties, such as icons, content descriptions, titles, and routes. Each object inside this class is like a character in our navigation story.
Putting it Together:
Imagine our app as a theater production. The NavHostController
is the director guiding the actors (composables) through different scenes (composables). The ActiveScreen
sealed class is our script, detailing the characters and their roles.
In our code, the DrawerNavGraph.kt
file acts as a script supervisor, telling the director (NavHostController) which scenes to include and when. So, when a user interacts with the navigation drawer, it's like flipping through pages in our script, and the director ensures the right scenes are played.
We’ve successfully implemented a navigation drawer using Material Design 3 in Jetpack Compose. Congratulations on navigating through the intricacies of building a sleek and functional UI.
I hope this article has provided valuable insights and assistance for your Android journey. Your support means a lot to me, so if you found this content helpful, please start following me and don’t hesitate to show some love with a hearty round of applause!👏
Your feedback fuels my passion for creating quality content. For any Android queries or just to connect, reach out on LinkedIn and Twitter.
Thanks for reading — looking forward to staying in touch!
Happy Coding!!