Exploring Jetpack Compose: Modal Drawer Layout

E

In the last post we took a look at the Top App Bar component within Jetpack Compose. Here we learnt how to create toolbar-like components to hold the title and menu options for screens in our applications. Because we created the navigationIcon for our top app bar within that article, it feels natural to now take a look at creating the navigation drawer itself using Jetpack Compose.

Note: There is a supporting video for this blog post:

Within Compose, this navigation drawer takes the form of the ModalDrawerLayout – a component which can slide in and out of view based on its opened / closed state. This component has a single Composable function:

@Composable
fun ModalDrawerLayout(
    drawerState: DrawerState,
    onStateChange: (DrawerState) -> Unit,
    gesturesEnabled: Boolean = true,
    drawerContent: @Composable() () -> Unit,
    bodyContent: @Composable() () -> Unit
)

The different arguments here allow us to configure our component and control what the drawer displays to our user:

  • drawerState – represents the current state of the drawer in the form of a DrawerState value. This can be either DrawerState.Closed or DrawerState.Opened
  • onStateChanged – a callback that is triggered whenever the value of drawerState is changed
  • gesturesEnabled – controls whether the drawer can be interacted with via the use of gestures
  • drawerContent – a Composable representing the content displayed within the drawer, such as menu items, header content etc
  • bodyContent – a Composable representing the content displayed outside of the drawer and within the body of the screen

Looking at the above, there isn’t too much that makes up the Modal Drawer Layout – we need to provide some content for the drawer itself, pass the Composable for body of our screen and handle state changes for when the drawer is opened / closed. With that said, let’s take a quick dive into building a ModalDrawerLayout for ourselves 🙌

To get started here we’re first going to start by creating some state represented by the state of the drawer – remember, this can be either Opened or Closed. We deconstruct this state so that we have a reference to the current state of the drawer along with a lamda to trigger any state changes that should occur.

 val (drawerState, onDrawerStateChange) = state { DrawerState.Closed }

With this in place we can begin by adding a Modal Drawer layout to our composition and assign the above values to both the drawerState and onStateChange properties. This means out drawer has a reference to the current state of the drawer, along with a lambda to trigger when the state changes.

MaterialTheme {
    ModalDrawerLayout(
        drawerState = drawerState,
        onStateChange = onDrawerStateChange
    )
}

We still don’t have the content of our screen set, so before we assign something to the bodyContent property we’re going to go ahead and create the body of our screen. We’ll keep this simple for now and use the content from the last post where we created our App Bar. The only difference here is that we’re going to provide our Content composable with a function parameter so that the navigationIcon can trigger opening of the drawer when clicked:

@Composable
fun Content(openDrawer: () -> Unit) {
    Column {
        TopAppBar(
            title = {
                Text(text = "AppBar")
            },
            color = Color.White,
            navigationIcon = {
                AppBarIcon(icon = imageResource(id = R.drawable.ic_menu_black_24dp)) {
                    openDrawer()
                }
            }
        )
    }
}

Now that we have the content of our screen, we’re going to go ahead and assign it to our drawer layout. Here we’ll pass in the onDrawerStateChange function as the parameter for our navigationIcon trigger – setting the DrawerState to Opened when done so:

MaterialTheme {
    ModalDrawerLayout(
        drawerState = drawerState,
        onStateChange = onDrawerStateChange,
        bodyContent = {
            Content { onDrawerStateChange(DrawerState.Opened) }
        }
    )
}

With the content set we’re next going to want to provide the body of the navigation drawer itself. Whist this would usually contain a collection of content (depending on the application), for examples sake we’re going to provide a simple Column with a single clickable Text item:

@Composable
private fun AppDrawer(
    closeDrawer: () -> Unit
) {
    Column {
        Ripple(bounded = true) {
            Clickable(onClick = closeDrawer) {
                Text("Click me to close")
            }
        }
    }
}

With this composable defined, we’ll now provide it as the composable for our drawerContent property. When doing so, we’ll pass the onDrawerStateChange function as the parameter for our Clickable onClick trigger. This means that when the Text component inside of our drawer is clicked, the drawer will be closed.

MaterialTheme {
    ModalDrawerLayout(
        drawerState = drawerState,
                onStateChange = onDrawerStateChange,
        drawerContent = {
            AppDrawer { onDrawerStateChange(DrawerState.Closed) }
        },
        bodyContent = {
            Content { onDrawerStateChange(DrawerState.Opened) }
        }
    )
}

In some cases we may want to control whether or not the drawer layout can be interacted with via the use of gestures. This can be done via the use of the gesturesEnabled property, which defaults to true if not passed into the composable function:

MaterialTheme {
    ModalDrawerLayout(
        drawerState = drawerState,
        onStateChange = onDrawerStateChange,
        gesturesEnabled = false,        
        drawerContent = {
            AppDrawer { onDrawerStateChange(DrawerState.Closed) }
        },
        bodyContent = {
            Content { onDrawerStateChange(DrawerState.Opened) }
        }
    )
}

With the above in place we’ve been able to composing a ModalDrawerLayout for our UI, housing navigational elements of our applications int he same way that we currently use the DrawerLayout in Android. Drawers are an incredibly popular component in applications, so I’m excited to see this being used out in the wild. Have you had a play with the ModalDrawerLayout yet, or do you have any questions before you get started? Please feel free to reach out in the comments!

[twitter-follow screen_name=’hitherejoe’ show_count=’yes’]

About the author

hitherejoe

Add Comment

By hitherejoe