The new M3 1.1 compose brings the ModalBottomSheet, however a BackHandler doesn't work when it is visible. How can this be implemented? I want a backpress to close the app, and not the modalsheet.
This is a bug in ModalBottomSheet, and even though Google marked it as fixed on Oct 31, 2023 and released it, the issue still occurs on Material3 1.2.0.
I have found a workaround though. It consists in intercepting the key event, doing the same checks that ModalBottomSheetWindow.dispatchKeyEvent
would do, and then delegating that to the OnBackPressedDispatcherOwner
manually.
@Composable
fun ModalBottomSheetWithBackHandling(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
sheetState: SheetState = rememberModalBottomSheetState(),
properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties(),
content: @Composable ColumnScope.() -> Unit,
) {
val scope = rememberCoroutineScope()
BackHandler(enabled = sheetState.targetValue != SheetValue.Hidden) {
// Always catch back here, but only let it dismiss if shouldDismissOnBackPress.
// If not, it will have no effect.
if (properties.shouldDismissOnBackPress) {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
onDismissRequest()
}
}
}
}
val requester = remember { FocusRequester() }
val backPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
ModalBottomSheet(
onDismissRequest = onDismissRequest,
modifier = modifier
.focusRequester(requester)
.focusable()
.onPreviewKeyEvent {
if (it.key == Key.Back && it.type == KeyEventType.KeyUp && !it.nativeKeyEvent.isCanceled) {
backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed()
return@onPreviewKeyEvent true
}
return@onPreviewKeyEvent false
},
properties = ModalBottomSheetDefaults.properties(
securePolicy = properties.securePolicy,
isFocusable = properties.isFocusable,
// Set false otherwise the onPreviewKeyEvent doesn't work at all.
// The functionality of shouldDismissOnBackPress is achieved by the BackHandler.
shouldDismissOnBackPress = false,
),
content = content,
)
LaunchedEffect(Unit) {
requester.requestFocus()
}
}