Nested Dropdown Menus in Compose
Skip’s open-source SkipUI library implements the SwiftUI API for Android. To do so, SkipUI leverages Compose, Android’s own modern, declarative UI framework.
While implementing SwiftUI’s Menu
, we discovered that Compose doesn’t build in support for nested dropdown menus. Googling revealed that we weren’t the only devs wondering how to present a sub-menu from a Compose menu item, but the answers we found didn’t meet our needs. The code below represents our own simple, general solution to nested dropdown menus in Compose.
Note: SkipUI’s implementation is tied to SwiftUI internals, so this is an untested and simplified port of the actual code.
// Simple menu model. You could expand this for icons, section
// headings, dividers, etc
class MenuModel(title: String, val items: List<MenuItem>): MenuItem(title, {})
open class MenuItem(val title: String, val action: () -> Unit)
// Display a menu model, whose items can include nested menu models
@Composable fun DropdownMenu(menu: MenuModel) {
val isMenuExpanded = remember { mutableStateOf(false) }
val nestedMenu = remember { mutableStateOf<MenuModel?>(null) }
val coroutineScope = rememberCoroutineScope()
// Action to replace the currently-displayed menu with the
// given one on item selection. The hardcoded delays are
// unfortunate but improve the user experience
val replaceMenu: (MenuModel?) -> Unit = { menu ->
coroutineScope.launch {
// Allow selection animation before dismiss
delay(200)
isMenuExpanded.value = false
// Prevent flash of primary menu on nested dismiss
delay(100)
nestedMenu.value = null
if (menu != null) {
nestedMenu.value = menu
isMenuExpanded.value = true
}
}
}
DropdownMenu(expanded = isMenuExpanded.value, onDismissRequest = {
isMenuExpanded.value = false
coroutineScope.launch {
// Prevent flash of primary menu on nested dismiss
delay(100)
nestedMenu.value = null
}
}) {
for (item in menu.items) {
DropdownMenuItem(text = { Text(item.title) }, onClick = {
item.action()
replaceMenu(item as? MenuModel)
})
}
}
}
You can see this in action in the Skip Showcase app’s menu playground:
We hope that you find this useful! If you have questions or suggestions for improvements, please reach out to us on Mastodon @skiptools@mas.to, via chat skiptools.slack.com, or in our discussion forums.