Jetpack Compose: ModalDrawer example

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.


MainActivity.kt

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()
    }
}
More android jetpack compose tutorials