I'm trying to hide the soft keyboard in an Android app with Compose UI. There are events emitted by ViewModel through the kotlin coroutines channel:
private val _screenEvents = Channel<ScreenEvent>(capacity = Channel.UNLIMITED)
val screenEvents: Flow<ScreenEvent> = _screenEvents.receiveAsFlow()
Events are send like this:
_screenEvents.trySend(event)
In Compose screen, events are collected in LaunchedEffect and any way to hide keyboard only works once, consecutive events are not collected.
val keyboard = LocalSoftwareKeyboardController.current
val inputService = LocalTextInputService.current
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
viewModel.screenEvents
.collect { event ->
when (event) {
is ScreenEvent.CollapseSearchResults -> {
// keyboard?.hide()
// inputService?.hideSoftwareKeyboard()
focusManager.clearFocus()
bottomSheetState.collapse()
}
...
}
}
}
TextField(value = "") {}
But if I swap the lines like this:
bottomSheetState.collapse()
// keyboard?.hide()
// inputService?.hideSoftwareKeyboard()
focusManager.clearFocus()
Everything works fine as many times as necessary. But the animations of collapsing bottom sheet and hiding keyboard are sequential and it doesn't suit me.
Can someone explain to me what is the problem and how can I solve it?
Edit
This issue is produced if TextField in UI has focus and soft keyboard is shown. The same if user holds BottomSheet while it's animation. It's turned out that BottomSheet animation is cancellable and it throws CancellationException in this cases.
Minimal, complete, reproducible example:
As a workaround, I've wrapped collapsing with try/catch
for now. I'd be glad If someone showed a better idea how to solve this problem.
focusManager.clearFocus()
try {
bottomSheetState.collapse()
} catch (e: Exception) {
e.printStackTrace()
}
CodePudding user response:
Currently, FlowCollector - bottomSheetState.collapse
is launch in LaunchedEffect
scope. thus, when an exception occurs in collapse flow collector, LaunchedEffect
scope is deactivated.
Try Composition CoroutineScope
as below.
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
viewModel.screenEvents
.collect { event ->
when (event) {
is ScreenEvent.CollapseSearchResults -> {
// keyboard?.hide()
// inputService?.hideSoftwareKeyboard()
focusManager.clearFocus()
scope.launch { bottomSheetState.collapse() }
}
...
}
}
}