Reputation: 63
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.
BackHandler { <breakpoint> }
. It is simply not triggered when user tries to hide keyboard.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.TextInputService.startInput()
, there are fields onEditCommand and onImeActionPerformed, they are also not triggered when user hides keyboardKeyboardActions(onAny = { <breakpoint> })
, hiding does not trigger it.android:windowSoftInputMode="stateAlwaysVisible"
in Manifest anyway. It did nothing.LaunchedEffect(key1 = focusRequester) { keyboardController.show() }
shows keyboard, but still hideable.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
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