Adding a dropdown menu to a Jetpack Compose TopAppBar
Adding a dropdown menu to a Jetpack ComposeTopAppBar
is quite easy, but simple examples aren't so easy to find! This post has a canonical example to use as a basis for your dropdown menu.
Add a Scaffold that Includes a TopAppBar
The first step is to create the Compose
view as a Scaffold
that assigns a new TopAppBar
to the topBar
property. This snippet will create a minimal top bar we can use as a starter.
val bodyContent = remember { mutableStateOf("Body Content Here") }
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "App Title")
},
actions = {
// TODO: Implement the menu
})
}) {
Column(
horizontalAlignment =
Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
.padding(20.dp)) {
Text(bodyContent.value)
}
}
Implement the menu
On line eight of the above snippit is a TODO item to implement the menu. This could be implemented in-line by adding the menu as a replacement for the TODO comment line, but that would result in a really long Composable
function that's difficult to read. Instead let's break that out into its own Composable
function. Below the code we'll discuss how it works.
@Composable
fun TopAppBarDropdownMenu(bodyContent: MutableState<String>) {
val expanded = remember { mutableStateOf(false) } // 1
Box(
Modifier
.wrapContentSize(Alignment.TopEnd)
) {
IconButton(onClick = {
expanded.value = true // 2
bodyContent.value = "Menu Opening"
}) {
Icon(
Icons.Filled.MoreVert,
contentDescription = "More Menu"
)
}
}
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false },
) {
DropdownMenuItem(onClick = {
expanded.value = false // 3
bodyContent.value = "First Item Selected" // 4
}) {
Text("First item")
}
Divider()
DropdownMenuItem(onClick = {
expanded.value = false
bodyContent.value = "Second Item Selected"
}) {
Text("Second item")
}
Divider()
DropdownMenuItem(onClick = {
expanded.value = false
bodyContent.value = "Third Item Selected"
}) {
Text("Third item")
}
Divider()
DropdownMenuItem(onClick = {
expanded.value = false
bodyContent.value = "Fourth Item Selected"
}) {
Text("Fourth item")
}
}
}
The structure of the menu uses the standard Compose DropDownMenu
composable embedded in a Box. There are a few interesting points to discuss, called out by comments in the above code.
- At
//1
we add a state variableremember
to inform the Composable menu whether it's open or close. Theremember {}
syntax is used to ensure the state variable survives the view being recomposed. - At
//2
theexpanded
state variable is set to true when the user taps on vertical ellipses icon at the top right of the screen. This causes theDropDownMenu
to be redrawn in the open state. - At
//3
theexpanded
state is set to false when the user taps on a menu item, which causes the menuComposable
to close. This action is repeated for each element in the menu. - As the menu closes, the code at
//4
sets the text value for thebodyContent
that was passed in as a parameter to the menu composable.
Point 4 may be a little confusing, so let's talk about it.
Take another look at the first code snippet and notice that this state variable was defined:
val bodyContent = remember { mutableStateOf("Body Content Here") }
Then the current value of this state variable was used as the body content for the Composable
view body of the Scaffold:
Column { // some details removed for clarity
Text(bodyContent.value)
}
And finally, the state variable was passed to the menu composable.
TopAppBarDropdownMenu(bodyContent)
Since bodyContent
is a MutableState<String>
rather than String
, it's a bit like passing a variable by reference--the called routine can update the .value
property of the state variable directly.
Get the Code
And here's where you can find the full code for this simple example app to see all the code snippets in the context of a simple (but working) example.