Introduction
In modern Android development, Jetpack Compose offers a streamlined way to build user interfaces with declarative, Kotlin-based code. One powerful component it includes is ModalDrawer
, a drawer that appears from the side of the screen and overlays the main content. This article explains a practical example of implementing a ModalDrawer
in Jetpack Compose to build a responsive, intuitive UI. This example demonstrates how to create a ModalDrawer
that can be toggled open and closed, allowing users to access additional options and navigation within an app.
This example code leverages Kotlin's coroutine capabilities for seamless drawer interactions and uses Compose components like Scaffold
, TopAppBar
, and ModalDrawer
. In this breakdown, we'll explore each part of the code, highlighting the roles of composable functions, drawer state management, and coroutine scope to ensure smooth animations and responsive design.
Code Breakdown: Setting Up the Main Activity
The main activity file, MainActivity.kt
, serves as the entry point for this Compose-based application. It extends AppCompatActivity
, the base class for activities that use the support library's action bar features. Inside onCreate
, the setContent
function is called to set up the Compose content. Here, GetScaffold()
is passed as the root composable function, which initializes the screen with a top app bar, main content, and a specified background color. The use of setContent
allows us to transition smoothly from the traditional XML-based layouts to Compose.
Building the Scaffold Layout with a Top Bar
The GetScaffold
function creates a Scaffold
, a container component in Compose that provides a structure for material design elements like the top bar, bottom navigation, and drawers. In this example, the top bar is defined with TopAppBar
, where the title text "Compose - ModalDrawer" is displayed. The app bar color is set with a custom purple color to give the app a unique look, and the title text is white for readability. This top bar remains fixed and provides context for the screen, while the main content (handled by MainContent
) sits below it.
Managing Drawer State and Coroutines
In MainContent
, we declare drawerState
, a mutable state that controls the ModalDrawer
's open or closed status. This state is remembered across recompositions with rememberDrawerState
, initialized with DrawerValue.Closed
. Additionally, rememberCoroutineScope
is used to obtain a coroutine scope within the composable context. Using coroutines with the drawer state lets us perform smooth, asynchronous drawer open and close actions with minimal boilerplate code, which is especially useful for responsive animations.
Creating the Drawer Layout with ModalDrawer
The main UI component, ModalDrawer
, takes in two key parameters: drawerState
and drawerContent
. drawerState
is used to toggle the drawer's visibility, while drawerContent
defines the items and layout within the drawer. In this example, drawerContent
is a Box
layout that centers an OutlinedButton
labeled "Close Drawer." This button is styled to take the full width of the drawer and uses scope.launch { drawerState.close() }
to close the drawer when clicked. By calling launch
, the button smoothly closes the drawer in a coroutine, enhancing the user experience with a non-blocking transition.
Setting Up Main Content with Open Drawer Button
The main content of the screen, specified in the content
parameter of ModalDrawer
, displays a column with a prompt and a button to open the drawer. The prompt text changes based on whether the drawer is open or closed, indicating to users how to interact with the drawer. The "Open Drawer" button is styled with a green background and initiates a coroutine that opens the drawer when clicked, again using scope.launch { drawerState.open() }
. This button allows users to explicitly open the drawer, complementing the swipe gesture for those who may prefer tapping.
Summary
This ModalDrawer
example demonstrates the simplicity and flexibility of building side drawers in Jetpack Compose using Kotlin. With state management and coroutine integration, this code creates a responsive UI that smoothly handles drawer interactions. This approach not only enhances user experience with quick, fluid animations but also simplifies the traditionally complex task of creating and managing drawers.
By understanding how to combine Scaffold
, ModalDrawer
, and coroutine-based state management, developers can build user-friendly, interactive UIs that maintain a clean code structure and provide users with intuitive navigation options. This example serves as a foundation for integrating more advanced features into Compose apps, making it an essential pattern for any Android developer.
package com.cfsuman.jetpackcompose
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.rememberDrawerState
import androidx.compose.ui.Alignment
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GetScaffold()
}
}
@Composable
fun GetScaffold(){
Scaffold(
topBar = {TopAppBar(
title = {Text(
"Compose - ModalDrawer",
color = Color.White)},
backgroundColor = Color(0xFF58427C)) },
content = {MainContent()},
backgroundColor = Color(0xFFEDEAE0)
)
}
@Composable
fun MainContent(){
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalDrawer(
drawerState = drawerState,
drawerContent = {
Box(
Modifier
.fillMaxSize()
.background(Color(0xFfF0F8FF))
.padding(12.dp)
.wrapContentSize(Alignment.TopStart)
){
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
onClick = { scope.launch { drawerState.close() } },
content = { Text("Close Drawer") }
)
}
},
content = {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = if (drawerState.isClosed)
"Swipe >> to open drawer"
else "Swipe <<< to close drawer"
)
Spacer(Modifier.height(25.dp))
Button(
onClick = { scope.launch { drawerState.open() } },
colors = ButtonDefaults.buttonColors(
backgroundColor = Color(0xFF8DB600)
)
) {
Text("Open Drawer")
}
}
}
)
}
@Preview
@Composable
fun ComposablePreview(){
//GetScaffold()
}
}
- jetpack compose - Card padding
- jetpack compose - Card background color
- jetpack compose - Card alignment
- jetpack compose - Card clickable
- jetpack compose - flowOf flow builder
- jetpack compose - Convert list to flow
- jetpack compose - Count down flow
- jetpack compose - Count up flow by ViewModel
- jetpack compose flow - Room add remove update
- jetpack compose flow - Room implement search
- jetpack compose - ViewModel Room edit update data
- jetpack compose - ViewModel Room delete clear data
- compose glance - How to create app widget
- jetpack compose - Icon from vector resource
- jetpack compose - IconButton from vector resource