multistackdeveloper
multistackdeveloper

Reputation: 63

Force soft keyboard presense in Compose

I am making search screen in POS app and want software keyboard to be always shown while TextField is present. Ideally I want to intercept event of user hiding keyboard to hide my search widget together with keyboard.

Currently I have

@Composable
fun Search(onHide: () -> Unit) {
    var query by rememberSaveable { mutableStateOf("") }
    BackHandler {
        onHide() //works only when keyboard is already hidden
    }
    Column(modifier = Modifier.fillMaxHeight()) {
        TextField(value = query, onValueChange = { query = it },
            modifier = Modifier.focusRequester(focusRequester))
        myItemsList.filter { it.title.startsWith(query) }.take(7).forEach {
            SearchLine(it)
        }
    }
    LaunchedEffect(key1 = focusRequester) {
        focusRequester.requestFocus()
        focusRequester.captureFocus()
//        keyboardController.show() //it is shown due to focus anyway
    }
}

For context, I use it like this:

fun Content() {
    var searchMode by rememberSaveable { mutableStateOf(true) } //FIXME false
    AppTheme {
        Surface {
            if (searchMode) {
                Search(onHide = { searchMode = false })
            } else {
                /* normally available activtiy contents */
            }
        }
    }
}

Bonus questions:

These are probably relevant, because if it is also not possible, is my best option using custom in-app keyboard? I'd rather not, because of some tricky locales like Japanese.


I tried:

  1. BackHandler { <breakpoint> }. It is simply not triggered when user tries to hide keyboard.
  2. Modifier for TextField with focusRequester.captureFocus(). Idk if it helps to capture focus, my TextField is only focusable thing present and always focused anyway, but it does not prevent user from hiding keyboard.
  3. Fiddled with TextInputService.startInput(), there are fields onEditCommand and onImeActionPerformed, they are also not triggered when user hides keyboard
  4. Same goes for KeyboardActions(onAny = { <breakpoint> }), hiding does not trigger it.
  5. KeyboardOptions supplied to TextField() does not have relevant options
  6. I don't want separate activity for search, but tried android:windowSoftInputMode="stateAlwaysVisible" in Manifest anyway. It did nothing.
  7. LaunchedEffect(key1 = focusRequester) { keyboardController.show() } shows keyboard, but still hideable.
  8. imm!!.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) with window token of activtiy, did nothing, from my understending it is irrelevant for Compose.

And probably more, I am dog tired already:(

Upvotes: 1

Views: 324

Answers (1)

Fluotin Halog
Fluotin Halog

Reputation: 108

"Hide Keyboard" keycode is 17179869184 (Key.Back), so you can prevent hide keyboard action by intercepting the key. Image

var value by remember { mutableStateOf("") }
var isFocused by remember { mutableStateOf(false) }

TextField(
    value = value,
    onValueChange = { value = it },
    modifier = Modifier
        .onPreInterceptKeyBeforeSoftKeyboard {
            if (it.key == Key.Back && isFocused) {
                true
            } else {
                false
            }
        }
        .onFocusChanged {
            isFocused = it.isFocused
            setSoftKeyboardVisibility(isFocused)
        }
)

private fun ComponentActivity.setSoftKeyboardVisibility(isVisible: Boolean) {
    currentFocus?.let { currentFocus ->
        with(WindowInsetsControllerCompat(window, currentFocus)) {
            if (isVisible) {
                show(WindowInsetsCompat.Type.ime())
            } else {
                hide(WindowInsetsCompat.Type.ime())
            }
        }
    }
}

Unfortunately, there are some cases that this solution does not cover. When gesture navigation bar feature is on, the keyboard is hidden by back swipe gesture. Image(Swipe Gesture)

There might also be other way to hide the keyboard, not only per device but software keyboards. So, I think implementing custom in-app keyboard is the best way to ensure keyboard visibility.

Edit:

I found that onPreInterceptKeyBeforeSoftKeyboard is not properly triggered on some devices like Pixel 6 or Emulators. So I had to find an additional means to ensure keyboard visibility.

Thanks to @trinadh thatakula, I came up with an idea to hide or show soft keyboard when WindowInsets of ime changes.

val isImeVisible = WindowInsets.isImeVisible
LaunchedEffect(isImeVisible) {
    setSoftKeyboardVisibility(isFocused)
}

WindowInsets.isImeVisible is marked as ExperimentalLayoutApi. so if you don't like it, you may use the following code.

val imeInsets = WindowInsets.ime
val isImeVisible by remember {
    derivedStateOf {
        imeInsets.getBottom(density) > 0
    }
}
LaunchedEffect(isImeVisible) {
    setSoftKeyboardVisibility(isFocused)
}

Upvotes: 0

Related Questions