IME padding lag in ModalBottomSheet
Hi 👋🏻 Thank you for your hard work on non-material components.
In my app, I have a ModalBottomSheet which presents a form containing an input field. The input field has a FocusRequester, and requestFocus() is called in a LaunchedEffect in order to show the keyboard. Sheet within the ModalBottomSheet has .imePadding() modifier. Unfortunately, in most cases (not always) this leads to a situation when first the enter animation is played behind the keyboard, and only then IME padding is applied. Any ideas how this can be fixed without introducing magic delays?
https://github.com/user-attachments/assets/45beff65-80f5-49d9-9066-37d83e6590fa
I suspect that this is a combination of a few things and might not be related to Unstyled, but let's see what can be done.
Can you let me know the version of Android you are running in the video, and which soft keyboard you are using?
- It's Android 13 (SDK 33)
- HeliBoard, but Samsung keyboard behaves in the same way
Could you share a minimum reproducible code so that i can check what is happening on my end?
val sheetState = rememberModalBottomSheetState(
initialDetent = Hidden,
)
ModalBottomSheet(state = sheetState) {
Scrim()
Sheet(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.imePadding()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(24.dp)
) {
val focusRequester = remember {
FocusRequester()
}
BasicTextField(
value = "This is a test", onValueChange = {},
modifier = Modifier
.focusRequester(focusRequester)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}
}
LaunchedEffect(Unit) {
delay(1000)
sheetState.targetDetent = FullyExpanded
}
https://github.com/user-attachments/assets/20a93510-9824-477e-8fbb-8bb69a8baa81
tween() animation spec with the duration lower than 100 ms prevents this, but this is a damn fast animation 😅
You shouldn't need to use any delays or animations. Something seems to be up. Let me investigate and get back to you asap :)
As a temporary solution you can use this to wait until the sheet is fully visible before focusing the text field:
LaunchedEffect(sheetState.isIdle) {
if(sheetState.targetDetent == FullyExpanded) {
focusRequester.requestFocus()
}
}
I am still investigating what is the cause of the delay, but this should be a much nicer UX for your app in the meantime
Thanks, Alex, sure. In my case it's just more complicated because the sheet is used to show navigation destinations, so the content doesn't know about the sheet state. For now, I decided to use a very fast animation.
IIRC you dont need to explicitly focus on the text field for it to gain focus. You could have the sheet itself focus its contents and the result should be the same. Not 100% sure though.
Anyhow, if the animation workaround works for you, im glad. Will keep you posted on any updates I have
By the way, if you're interested in a navigator utilizing ModalBottomSheet: https://github.com/Radiokot/4money/blob/main/app/src/main/java/ua/com/radiokot/money/BottomSheetNavigation.kt
It uses a single sheet as a host, and if there are few back stack items, then the sheet shows the top one.
Added a new verticalOffset parameter to the sheets for handling such cases. It allows you to contribute to the internal offset of the sheet for when setting an imePadding() is not working as expected (such as your case).
| Before | After |
|---|---|
The updated version of your code would be:
val sheetState = rememberModalBottomSheetState(
initialDetent = Hidden,
)
ModalBottomSheet(state = sheetState) {
Scrim()
Sheet(
modifier = Modifier
.fillMaxWidth()
.background(Color.White),
// .imePadding() <-- remove this
verticalOffset = WindowInsets.ime.asPaddingValues().calculateBottomPadding(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.padding(24.dp)
) {
val focusRequester = remember {
FocusRequester()
}
BasicTextField(
value = "This is a test", onValueChange = {},
modifier = Modifier
.focusRequester(focusRequester)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}
}
LaunchedEffect(Unit) {
delay(1000)
sheetState.targetDetent = FullyExpanded
}
Thank you, Alex! Looking forward to trying it out when the new version is released 🤝🏻
@alexstyl
Alex, the fix works for IME padding, but it doesn't go well with windowInsetsPadding modifier. In my app, I show bottom sheets with custom background and long lists, which are drawn behind the navigation bar.
If I combine imeAware = true for the Sheet and windowInsetsPadding(WindowInsets.navigationBars) modifier for the inner content, I get redundant bottom space when the keyboard is shown:
However, if I use imePadding() modifier on the Sheet, it works well, but the original animation issue is of course present in this case:
Seems that imeAware = true doesn't consume the bottom navigation bar inset when the keyboard is shown, unlike how it works with the imePadding() modifier.
I can have the sheet consume the ime padding.
Any chance you can provide some code so that I can test out on my side? doesn't have to be working, but rather pseudo code with modifiers you use and in how many components ie
This will speed up the process
ModalBottomSheet {
Sheet(imeAware = true, modifier = Modifier.navigationBarsPadding()) {
}
}
Thank you. Here's a snippet with imports:
https://gist.github.com/Radiokot/da3a3ef6abfa62166f55d1f6f985e5bf
It illustrates the unwanted space brought by navigation bars padding modifier when the keyboard is shown:
This was exactly what I needed. Thanks!
I just pushed a hot fix (https://github.com/composablehorizons/compose-unstyled/releases/tag/1.38.1). Could you kindly let me know if it fixes the issue for you?
Many thanks for your work, Alex. It works as expected now 👏🏻 Please let me know if you accept Bitcoin or PayPal donations, I think the framework you built deserves financial support.
Awesome! Glad we got it working :)
I don't accept donations from individuals. If you do want to support the open source project, you can buy the UI Kit I am working on at: https://composables.com/ui-kit
It is the styled version of Compose Unstyled that works for all platforms not just Android.