How to prevent ModalBottomSheet from overlapping with the system buttons?

3.4k Views Asked by At

I'm trying to add a ModalBottomSheet from Material3 into my app. I have a main screen implemented like this:

@Composable
fun MainScreen() {

    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Scaffold(
            content = {},
            modifier = Modifier.fillMaxSize()
        )
    }
    SignInBottomSheet()
}

And SignInBottomSheet:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignInBottomSheet(
) {

    ModalBottomSheet(
        onDismissRequest = {}
    ) {
        Column(
            modifier = Modifier.align(Alignment.CenterHorizontally)
        ) {
            Button(
                modifier = Modifier
                    .width(200.dp)
                    .height(70.dp)
                    .padding(bottom = 10.dp),
                onClick = { /*TODO*/ }
            ) {
                Text(
                    text = "Log in",
                    style = MaterialTheme.typography.bodyLarge,
                    fontWeight = FontWeight.Bold
                )
            }
            Button(
                onClick = { /*TODO*/ },
                modifier = Modifier
                    .width(200.dp)
                    .height(70.dp)
                    .padding(bottom = 10.dp),
            ) {
                Text(
                    text = "Continue with Google",
                    style = MaterialTheme.typography.bodyLarge,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

The bottom sheet is visible when the app starts and when I tap outside of the bottom sheet, the bottom sheet is collapsed, but it stays at the bottom of the screen overlapping part of the system buttons.

I haven't added any remember object because I get the same behavior with the one, only difference is that the bottom sheet disappears after a moment (but still the snap is visible).

Bellow are the images of the opened and closed bottom sheet (take a close look at the bottom of the screen).

enter image description here

enter image description here

2

There are 2 best solutions below

0
On BEST ANSWER

Looks like this was resolved in Compose Material 3 version 1.2.0-alpha02.

Look under the following section in the release notes:

Add window insets parameter to ModalBottomSheet.

However, given it's an alpha library, not everyone will want to update to it.

I created an issuetracker for them to backport the fix into 1.1.0.

See: https://issuetracker.google.com/issues/284450382

0
On

By Default they are not hiding the ModalBottomSheet, In document also they maintain the if condition to makes work around.

For your reference : https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ModalBottomSheet(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.SheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,kotlin.Function0,kotlin.Function1)

We need to maintain the state - var openBottomSheet by rememberSaveable { mutableStateOf(false) }

Change the state based on the Bottomsheet dimiss and open.

By using if condition or Animated Visibility to fix the Bottom Sheet overlap issue.

    var openBottomSheet by rememberSaveable { mutableStateOf(false) }
    var skipPartiallyExpanded by remember { mutableStateOf(false) }
    val scope = rememberCoroutineScope()
    val bottomSheetState = rememberModalBottomSheetState(
        skipPartiallyExpanded = skipPartiallyExpanded
    )

// App content
Column(
    modifier = Modifier.fillMaxSize(),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center
) {
    Row(
        Modifier.toggleable(
            value = skipPartiallyExpanded,
            role = Role.Checkbox,
            onValueChange = { checked -> skipPartiallyExpanded = checked }
        )
    ) {
        Checkbox(checked = skipPartiallyExpanded, onCheckedChange = null)
        Spacer(Modifier.width(16.dp))
        Text("Skip partially expanded State")
    }
    Button(onClick = { openBottomSheet = !openBottomSheet }) {
        Text(text = "Show Bottom Sheet")
    }
}

// Sheet content
if (openBottomSheet) {
    ModalBottomSheet(
        onDismissRequest = { openBottomSheet = false },
        sheetState = bottomSheetState,
    ) {
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
            Button(
                // Note: If you provide logic outside of onDismissRequest to remove the sheet,
                // you must additionally handle intended state cleanup, if any.
                onClick = {
                    scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
                        if (!bottomSheetState.isVisible) {
                            openBottomSheet = false
                        }
                    }
                }
            ) {
                Text("Hide Bottom Sheet")
            }
        }
        var text by remember { mutableStateOf("") }
        OutlinedTextField(value = text, onValueChange = { text = it })
        LazyColumn {
            items(50) {
                ListItem(
                    headlineContent = { Text("Item $it") },
                    leadingContent = {
                        Icon(
                            Icons.Default.Favorite,
                            contentDescription = "Localized description"
                        )
                    }
                )
            }
        }
    }
}

Instead of If condition, we can use AnimatedVisibility also.