https://kotlinlang.org logo
#compose
Title
# compose
n

natario1

10/13/2023, 10:46 AM
Was anyone able to make ModalBottomSheet work with a focusable text field inside? It has all sorts of bugs when the field gets focus - sometimes it jumps too high, sometimes too low, depending on content height and on the
skipPartiallyExpanded
flag. No combination seems to be safe.
👀 1
z

Zoltan Demant

10/13/2023, 11:34 AM
I think the issue is related to the animation and how it plays in response to the IME showing/hiding, as per this thread (workaround is for M2 only, unfortunately). I think theres a bug report somewhere? Fwiw, seeing similar behavior on my end; if a TextField (or whatever) has focus with IME shown prior to the sheet showing up, it will jump around before settling at the bottom of the screen. I assume this is the same thing youre describing, just coming at it from the opposite end of the spectrum. You may be able to smoothen out the edges by deferring when the keyboard is shown / TextField is focused; please let me know if you do try that and if it works!
a

ascii

10/13/2023, 12:24 PM
z

Zoltan Demant

10/13/2023, 12:32 PM
I think thats a different, although quite similar issue @ascii. Maybe its what @natario1 was looking for though? What I was referring to is the sheet basically showing up in the wrong spot (same as the recording in the issue you linked) but then jumping to the bottom awkwardly. Maybe its even the fix that causes this 😅
n

natario1

10/13/2023, 1:09 PM
Thank you guys. The behaviour I’m seeing is pretty weird and I’m not sure which ticket it belongs to. I don’t think that fix was released anyway? I tried latest alpha but no changes.
FWIW I came up with this. I use
ModalBottomSheetContainer
at the root of the app and then I can use my version of
ModalBottomSheet
from anywhere as if it were a modal even though it uses the scaffold under the hood. Way less buggy. In fact, at a first glance I don’t see why modals can’t be implemented like this by default. Material3 is using a custom view, a composition nested inside the main composition, window manager.addView(), with all the inevitable problems about interop, window insets, and so on.
Copy code
private class ModalBottomSheetProvider {
    val content: MutableState<(@Composable ColumnScope.() -> Unit)?> = mutableStateOf(null)
    val onDismiss: MutableState<(() -> Unit)?> = mutableStateOf(null)
}

private val LocalModalBottomSheetProvider = compositionLocalOf<ModalBottomSheetProvider> { error("No provider") }

@Composable
fun ModalBottomSheet(
    onDismiss: () -> Unit,
    content: @Composable ColumnScope.() -> Unit
) {
    val provider = LocalModalBottomSheetProvider.current
    DisposableEffect(content) {
        provider.content.value = content
        onDispose { provider.content.value = null }
    }
    DisposableEffect(onDismiss) {
        provider.onDismiss.value = onDismiss
        onDispose { provider.onDismiss.value = null }
    }
}

@Composable
fun ModalBottomSheetContainer(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    val provider = remember { ModalBottomSheetProvider() }

    val state = rememberBottomSheetScaffoldState(
        bottomSheetState = rememberModalBottomSheetState()
    )

    val sheetValue = state.bottomSheetState.currentValue
    LaunchedEffect(sheetValue) {
        if (sheetValue == SheetValue.Hidden) provider.onDismiss.value?.invoke()
    }

    val sheetContent = provider.content.value
    LaunchedEffect(sheetContent) {
        if (sheetContent != null) state.bottomSheetState.expand()
        else state.bottomSheetState.hide()
    }

    CompositionLocalProvider(LocalModalBottomSheetProvider provides provider) {
        BottomSheetScaffold(
            scaffoldState = state,
            sheetContent = { provider.content.value?.invoke(this) },
            // Should help in skipping partially expanded state.
            // The flag in rememberModalBottomSheetState() throws when using scaffold.
            sheetPeekHeight = 0.dp,
            modifier = modifier,
            content = { content() }
        )
    }
}
a

ascii

10/13/2023, 1:35 PM
Oh that's all M2 code, while the issue I linked is for M3
1
n

natario1

10/13/2023, 1:38 PM
Why do you say that? I am using M3, with import androidx.compose.material3.* on top.
To be clear the code above is not a repro for the issue, it’s a workaround for it, just in case it’s useful for anyone. It uses a M3
BottomSheetScaffold
under the hood while letting you call
ModalBottomSheet { … }
even from nested hierarchies. Of course it has drawbacks (for example, composition locals are not kept). But still better than text field jumping around