How to close the virtual keyboard from a Jetpack Compose TextField? - android

I'm using the Jetpack Compose TextField and I want to close the virtual keyboard when the user press the the action button (imeActionPerformed parameter).
val text = +state { "" }
TextField(
value = text.value,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done,
onImeActionPerformed = {
// TODO Close the virtual keyboard here <<<
}
onValueChange = { s -> text.value = s }
)

You can use the LocalSoftwareKeyboardController class to control the current software keyboard and then use the hide method:
var text by remember { mutableStateOf(TextFieldValue("Text")) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
label = { Text("Label") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {keyboardController?.hide()})
)
This solution closes the keyboard without removing the focus from the current TextField.
Just to highlight the difference with:
val focusManager = LocalFocusManager.current
focusManager.clearFocus()
This code closes the keyboard removing the focus from the TextField.

Starting from compose 1.0.0-alpha12 (and still valid in compose 1.3.1) the onImeActionPerformed is deprecated and suggested approach is to use keyboardActions with combination of keyboardOptions:
val focusManager = LocalFocusManager.current
OutlinedTextField(
value = ...,
onValueChange = ...,
label = ...,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done, keyboardType = KeyboardType.Password),
)
focusManager.clearFocus() will take care of dismissing the soft keyboard.

In 1.0.0 you can either use SoftwareKeyboardController or FocusManager to do this.
This answer focuses on their differences.
Setup:
var text by remember { mutableStateOf("")}
TextField(
value = text,
onValueChange = { text = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { /* TODO */ }),
)
SoftwareKeyboardController:
Based on #Gabriele Mariottis answer.
val keyboardController = LocalSoftwareKeyboardController.current
// TODO =
keyboardController?.hide()
This only closes the keyboard, but does NOT clear the focus from any focused TextField (note the cursor & thick underline).
FocusManager:
Based on #azizbekians answer.
val focusManager = LocalFocusManager.current
// TODO =
focusManager.clearFocus()
This closes the keyboard AND clears the focus from the TextField.

Hiding the keyboard on button click
To add with Gabriele Mariotti's solution, if you want to hide the keyboard conditionally, say after a button click, use this:
keyboardController?.hide()
For example, hide the keyboard after clicking the Add button:
var newWord by remember { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current
// Setup the text field with keyboard as provided by Gabriele Mariotti
...
Button(
modifier = Modifier
.height(56.dp),
onClick = {
if (!newWord.trim().isNullOrEmpty()) {
wordViewModel.onAddWord(newWord.trim())
newWord = ""
keyboardController?.hide()
}
...

Edit after alpha-12 release:
See #azizbekian response.
Pre-alpha-12 response
I found the solution here :)
fun hideKeyboard(activity: Activity) {
val imm: InputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
var view = activity.currentFocus
if (view == null) {
view = View(activity)
}
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
I just need to call the function above from my component:
// getting the context
val context = +ambient(ContextAmbient)
// textfield state
val text = +state { "" }
TextField(
value = text.value,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done,
onImeActionPerformed = {
if (imeAction == ImeAction.Done) {
hideKeyboard(context as Activity)
}
}
onValueChange = { s -> text.value = s }
)

I found a way to shut him down in the CoreTextField,use TextInputService to control the switch
val focus = LocalTextInputService.current
var text by remember{ mutableStateOf("")}
TextField(
value = text,
onValueChange = { text = it },
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done, keyboardType = KeyboardType.Text),
keyboardActions = KeyboardActions(onDone = { focus?.hideSoftwareKeyboard() }),
singleLine = true
)

implementation 'androidx.compose.material3:material3:1.0.0-alpha02'
Text Field With Hide Keyboard On Ime Action
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun TextFieldWithHideKeyboardOnImeAction() {
val keyboardController = LocalSoftwareKeyboardController.current
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
// do something here
}
)
)
}

Related

Android compose test TextField input type

How would you go about testing what input type TextField uses, for example if I wanted to test if user input has an alphanumeric keyboard type or numeric.
I can see that in SemanticProperties there is ImeAction, but I can't see anything I could use to check KeyboardOptions that you set in TextField.
You can use something like:
val platformTextInputService = mock<PlatformTextInputService>()
val textInputService = TextInputService(platformTextInputService)
composeRule.setContent {
CompositionLocalProvider(
LocalTextInputService provides textInputService
) {
val text = remember { mutableStateOf("") }
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = text.value,
onValueChange = { text.value = it },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
)
)
}
}
composeRule.onNodeWithTag(TextfieldTag).performClick()
composeRule.runOnIdle {
verify(platformTextInputService, atLeastOnce()).startInput(
value = any(),
imeOptions = eq(
ImeOptions(
keyboardType = KeyboardType.Number,
)
),
onEditCommand = any(),
onImeActionPerformed = any()
)
}

Remove keypad along with ModalBottomSheetLayout in Jetpack compose

After clicking outside BottomSheet, BottomSheet is hidden but the keypad remains persistent.
I want to remove the keypad as soon as BottomSheet is hidden in ModalBottomSheetLayout
After clicking the text in compose keypad pops up
After clicking outside bottom sheet, bottom sheet disappears but keypad persists
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
Column {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
label = { Text("Text") },
onValueChange = { text = it }
)
var text1 by remember { mutableStateOf("") }
OutlinedTextField(
value = text1,
label = { Text("Text1") },
onValueChange = { text1 = it }
)
OutlinedButton(
onClick = { click() },
) { Text(text = "SUBMIT") }
}
},
sheetState = state,
content = {
Greeting(state = state, scope = scope)
}
)
You need to add keyboard options and actions for the text fields. The keyboard options setup what type of keyboard and action you have on the keyboard. The onAction you pass the lambda to hide the bottom sheet and clear the focus:
val sheetState = rememberModalBottomSheetState(
skipHalfExpanded = true,
initialValue = ModalBottomSheetValue.Hidden
)
val focusRequester = remember { FocusRequester() }
OutlinedTextField(
value = text,
label = { Text("Text") },
onValueChange = { text = it },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
coroutineScope.launch {
focusRequester.freeFocus()
sheetState.hide()
}
}
)
)

Jetpack compose TextField soft keyboard obscures text entry

When initially clicking the text field the whole view correctly shifts upwards so the keyboard is just below text entry.
After pressing return on the keyboard nothing on the screen moves and it leaves the cursor under the keyboard out of vision.
I had some other TextField issues that were solve by using the accompanist library. There is currently an assigned issue for it here. Here is the composable for the textField:
#Composable
fun EditTextContent(
label: String,
text: String,
isError: Boolean,
modifier: Modifier = Modifier,
singleLine: Boolean,
inputType: KeyboardType = KeyboardType.Text,
onTextChanged: (String) -> Unit
) {
val focusManager = LocalFocusManager.current
OutlinedTextField(
modifier = modifier.navigationBarsWithImePadding().testTag(label),
value = text,
onValueChange = {
onTextChanged(it)
},
label = { Text(label) },
singleLine = singleLine,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
keyboardOptions = KeyboardOptions(keyboardType = inputType),
isError = isError,
trailingIcon = {
if (isError) {
Icon(
painter = painterResource(id = R.drawable.ic_warning),
modifier = Modifier.size(25.dp),
contentDescription = null
)
} else {
null
}
},
)
}
Activity in the manifest is set to android:windowSoftInputMode="adjustPan" since adjustResize does nothing for some reason. Any help would be appreciated.

Jetpack Compose Number Input in to TextField

I am currently unable to capture user input in to a textfield when the KeyboardType of the keyboard is set to KeyboardType.Number.
If the keyboard is set to KeyboardType.Text, the Textfield updates as expected, however when set to KeyboardType.Number, the Textfield fails to update.
Why is this? and how can I change my code so that when the Textfield is clicked, a Number Keyboard is displayed ,and, when numbers are pressed, the relevant numbers are updated in the Textfield.
The following code DOES NOT update the textfield (When set to KeyboardType.Number)...
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
value.value = it
}
TextField(
value = text.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
onValueChange = change
)
}
The following code does update the textfield (When set to KeyboardType.Text)...
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
text.value = it
}
TextField(
value = value.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
onValueChange = change
)
}
Many Thanks
You are supposed to update text.value, not value.value there is a typo in your code change it to this.
#Composable
fun MyNumberField() {
var text = remember { mutableStateOf("")}
val change : (String) -> Unit = { it ->
value.value = it // you have this which is not correct and I don't think it even compiled
text.value = it // it is supposed to be this
}
TextField(
value = text.value,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
onValueChange = change
)
}

What is the simplest way to set the focus order in Jetpack Compose?

I have a column of TextFields, something like:
Column {
TextField(
value = ...,
onValueChange = { ... },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.next),
)
TextField(
value = ...,
onValueChange = { ... },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.next),
)
.
.
.
}
I would like to have the focus on each TextField move to the next when the user press Tab, or the next button on the keyboard. Currently pressing Tab inserts a tab into the TextField. Pressing the next button does nothing. I can create a FocusRequester for each TextField and set the keyboardActions onNext to request focus on the next field for each one. This is a little tedious and it doesn't address the Tab behavior.
I recently found this article: https://medium.com/google-developer-experts/focus-in-jetpack-compose-6584252257fe
It explains a different way to handle focus that is quite a bit simpler.
val focusManager = LocalFocusManager.current
TextField(
modifier = Modifier
.onPreviewKeyEvent {
if (it.key == Key.Tab && it.nativeKeyEvent.action == ACTION_DOWN){
focusManager.moveFocus(FocusDirection.Down)
true
} else {
false
}
},
value = text,
onValueChange = { it -> text = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
)
)
Not sure if it's the easier way, but you can create a FocusRequester object for each field and request the focus following the order that you want.
#Composable
fun FocusRequestScreen() {
// Create FocusRequesters... (you can use createRefs function)
val focusRequesters = List(3) { FocusRequester() }
Column {
TextFieldWithFocusRequesters(focusRequesters[0], focusRequesters[1])
TextFieldWithFocusRequesters(focusRequesters[1], focusRequesters[2])
TextFieldWithFocusRequesters(focusRequesters[2], focusRequesters[0])
}
}
#Composable
private fun TextFieldWithFocusRequesters(
focusRequester: FocusRequester,
nextFocusRequester: FocusRequester
) {
var state by rememberSaveable {
mutableStateOf("Focus Transition Test")
}
TextField(
value = state,
onValueChange = { text -> state = text },
// Here it is what you want...
modifier = Modifier
.focusOrder(focusRequester) {
nextFocusRequester.requestFocus()
}
,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
)
}
I get this code from here. It didn't solve the tab issue though... :(
About the order you can check the #nglauber answer.
To use the Tab key you can use the onKeyEvent modifier.
TextField(
modifier = Modifier
.focusRequester(focusRequester)
.onKeyEvent {
if (it.key.keyCode == Key.Tab.keyCode){
focusRequesterNext.requestFocus()
true //true -> consumed
} else false },
value = text,
onValueChange = { it -> text = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(
onNext = {focusRequesterNext.requestFocus()}
)
)
I'm currently on compose_version = '1.0.2'.
Focus is not moved when you press the next button because, although the default keyboard action is to move focus to the next one, compose seems doesn't know which one should be the next one. Creating FocusRequester s for each item and set their focus order via Modifier.focusOrder() {} could work (btw, no need to set the keyboardActions's onNext to request focus if you are choosing this way), but since your TextFields are in the same Column, you can just set keyboardActions to tell compose move the focus to the one in the down direction. Something like:
Column {
val focusManager = LocalFocusManager.current
TextField(
value = "", onValueChange = {},
keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
)
)
TextField(
value = "", onValueChange = {},
keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
)
)
TextField(
value = "", onValueChange = {},
keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) }
)
)
}
After you did this, the next button on the IME keyboard should works.
For the Tab key, since TextField doesn't deal with Tab key automately, so you might want to use focusManager inside Modifier.onKeyEvent{} to move focus in the same way as the above example did.
Set singleLine , please try
OutlinedTextField(
...
singleLine = true,
)
Simple example
#Composable
fun Test() {
val focusManager = LocalFocusManager.current
var text1 by remember {
mutableStateOf("")
}
var text2 by remember {
mutableStateOf("")
}
Column() {
OutlinedTextField(value = text1, onValueChange = {
text1 = it
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Text
),
keyboardActions = KeyboardActions(
onNext ={
focusManager.moveFocus(FocusDirection.Down)
}
),
singleLine = true
)
OutlinedTextField(value = text2, onValueChange = {
text2 = it
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Text
),
keyboardActions = KeyboardActions(
onNext ={
focusManager.moveFocus(FocusDirection.Down)
}
),
singleLine = true
)
}
}

Categories

Resources