Home > Blockchain >  Hiding software keyboard in Compose broke Kotlin Flow/Channel events collection
Hiding software keyboard in Compose broke Kotlin Flow/Channel events collection

Time:04-04

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: enter image description here

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() }
                }
                ...
            }
        }
}
  • Related