Android Compose show and hide keyboard - android

I have a composable function with TextField:
val focusManager = LocalFocusManager.current
TextField(
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
}
)
)
and I need to show keyboard from inside of composable function as well as outside of it when I click on other button which is not part of composable content. Basically I wanna call hideKeyboard() from my fragment.
I tried to use livedata inside composable:
val shouldShowKeyBoard by shouldShowSearchKeyBoard.observeAsState()
and I can do focusManager.clearFocus() to hide keyboard but I'm not sure how to show it programmatically for specific compose TextField
What's the "compose" way to manage hide/show keyboard?

You can perform some action on state changes and you can do it using the side effects.
For example you can use the LaunchedEffect function, where as a key you can pass a state you want to listen.
LaunchedEffect(booleanValue) {
//...do something
}
You can use a ViewModel to set a boolean value and something like:
// initialize focus reference to be able to request focus programmatically
val focusRequester = remember { FocusRequester() }
LaunchedEffect(viewModel.showKeyboard) {
focusRequester.requestFocus()
}
TextField(
value = text,
onValueChange = {
text = it },
modifier = Modifier
// add focusRequester modifier
.focusRequester(focusRequester)
)
Just a note:
to hide the keyboard you can also use:
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
//...
keyboardActions = KeyboardActions(
onSearch = { keyboardController?.hide() }
)
Use the method focusManager.clearFocus() to dismiss the keyboard and clear the focus.

to add on Gabriele's answer, you will need val focusRequester = remember { FocusRequester() }
or you will get an exception

Related

How to select all on focus in BasicTextField

I'm trying make BasicTextField select all contents when user taps on it.
This happens automatically if user will long press on the contents of BasicTextField but I want it happening without the long press.
There's a thread Select all text of TextField in Jetpack Compose but it utilizes TextFieldValue object which is hidden in BasicTextField but can be passed in TextField composable.
You can use the TextFieldValue also with a BasicTextField.
Instead of using the onFocusChanged modifier you can use the interactionSource and change the selection range depending on it.
Something like:
var textFieldValue by remember { mutableStateOf(TextFieldValue("Custom text")) }
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
LaunchedEffect(isFocused) {
val endRange = if (isFocused) textFieldValue.text.length else 0
textFieldValue = textFieldValue.copy(
selection = TextRange(
start = 0,
end = endRange
)
)
}
BasicTextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
interactionSource = interactionSource
)

Keep text in BasicTextField on back navigation

I have a BasicTextField in my jetpack compose function. When i click(user has input some text into the textfield by now) on a button to navigate to another composable in my NavHost, and from that new view click on back to the composable which i came from which has the textfield, the textfield is empty. I want to keep the text that the user typed in before navigating, but I can't figure it out how. Have looked here but found no answer.
Suggestions?
Here is my code:
#Composable
fun SearchBar(
modifier: Modifier = Modifier,
onSearch: (String) -> Unit = {}
) {
var text by remember {
mutableStateOf("")
}
Box(modifier = modifier) {
BasicTextField(
value = text,
onValueChange = {
text = it
onSearch(it)
},
maxLines = 1,
singleLine = true,
textStyle = TextStyle(color = Color.Black),
modifier = Modifier
.fillMaxWidth()
.shadow(5.dp, CircleShape)
.background(Color.White, CircleShape)
)
}
}
remember saves the value over recomposition (i.e., when your state changes, your composable automatically recomposes with the new state).
As per the Restore UI State guide, you can replace remember with rememberSaveable to save your state across:
Configuration changes
Process death and recreation
Your composable being put on the NavHost back stack
As well as any other case where your SearchBar could be removed from composition and then re-added (such as if you were using in Accompanist's Pager or in a LazyColumn or LazyRow).
var text by rememberSaveable {
mutableStateOf("")
}

How to Disable ImeAction/Button on the Soft Keyboard when TextField is Empty

I have this TextFied Composable with both KeyboardOptions and KeyboardActions.
#Composable
fun TodoInputText(...) {
val keyboardController = LocalSoftwareKeyboardController.current
TextField( ....
onValueChange = onTextChanged,
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {
onImeAction()
keyboardController?.hide()
}))}
The TextField is working with the Done Action but I need to disable Done ImeAction on the keyboard whenever the TextFied is empty as illustrated on this GIF
I have already extracted a state to check if the TextField is empty.
#Composable
fun TodoItemEntryInput(...) {
//hold state for TextField
val (text, setText) = remember { mutableStateOf("") }
val isTextBlank = text.isNotBlank()
//declare lambda function submit that handles a submit event when done is pressed
val submitAction = { .... }
TodoItemInput(
text = text,
onTextChange = setText,
submitAction = submitAction,
)}
Now my question is how can I use the isTextBlank state to disable or Gray-out the Done ImeAction whenever the text is empty. This is to avoid bugs if the user enters blank text - I found input verification not very optimal for this case.
This is impossible in regular Android, so with Jetpack Compose this task also cannot be solved.
All you can do is check if the text in the onDone callback is valid, continue if it is, and show an error if it is not.

How to know when a Composable like TextField gains focus?

I want to trigger a function upon my TextField's focus-gain, but I am unable to find something like a listener.
You can use the onFocusChanged modifier
TextField(
value = text,
onValueChange = { text = it},
modifier = Modifier.onFocusChanged {
if (it.isFocused){
//...
}
}
)

Prevent the keyboard from appearing in a Jetpack Compose app

I'm making a calculator to learn Compose, so I placed my own number buttons on screen and I wanted to prevent the soft keyboard from appearing.
Here is my repo: https://github.com/vitor-ramos/CalculadorCompose
I noticed in TextFieldImpl.kt there is a modifier to show the keyboard, so I tried to clone the code and remove the line: keyboardController.value?.showSoftwareKeyboard() I know it's not a good idea to duplicate code like that, but I wanted to give it a try, and it didn't work. As you can see in the original code below there's a TODO saying it should be handled by BaseTextField, but I looked in it's code and didn't find where it shows or hides the keyboard.
val textFieldModifier = modifier
.focusRequester(focusRequester)
.focusObserver { isFocused = it.isFocused }
.clickable(indication = null) {
focusRequester.requestFocus()
// TODO(b/163109449): Showing and hiding keyboard should be handled by BaseTextField.
// The requestFocus() call here should be enough to trigger the software keyboard.
// Investiate why this is needed here. If it is really needed, instead of doing
// this in the onClick callback, we should move this logic to the focusObserver
// so that it can show or hide the keyboard based on the focus state.
keyboardController.value?.showSoftwareKeyboard()
}
I found in this question that with views I can extend EditText and change the functionality, but I haven't found a equivalent for Compose: Android: Disable soft keyboard at all EditTexts
public class NoImeEditText extends EditText {
public NoImeEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onCheckIsTextEditor() {
return false;
}
}
I have tested Arun Pdiyan solution and works like a charm with null LocalTextInputService (in my case I read data from device attached Barcode reader)
CompositionLocalProvider(
LocalTextInputService provides null
) {
TextField(
value = barcodeReaderService.readedText.value,
onValueChange = { textState.value=it },
label = { Text("The Label") }
)
}
Explanation
I created a Composable ReadonlyTextField, that places a invisible box in front of the text field. The box has the same size as the text field.
With that workaround you can't focus the text field anymore, so no keyboard appears. In order to apply custom click handling, i added a onClick to the Box-Modifier.
This is not really a clean solution, but a good workaround.
Implementation of ReadonlyTextField
#Composable
fun ReadonlyTextField(
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier,
onClick: () -> Unit,
label: #Composable () -> Unit
) {
Box {
TextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
label = label
)
Box(
modifier = Modifier
.matchParentSize()
.alpha(0f)
.clickable(onClick = onClick),
)
}
}
Usage of ReadonlyTextField
#Composable
fun App() {
val textState = remember { mutableStateOf(TextFieldValue()) }
Column {
ReadonlyTextField(
value = textState.value,
onValueChange = { textState.value = it },
onClick = {
// custom click handling (e.g. open dialog)
},
label = {
Text(text = "Keyboardless Input")
}
)
}
}
A complete integrated example can be found in my medium post:
https://caelis.medium.com/jetpack-compose-datepicker-textfield-39808e42646a
Credits also go to this stackoverflow answer:
Jetpack Compose: Disable Interaction with TextField
You can hide keyboard on compose by providing TextInputService to TextField. You can implement your TextInputService or just pass it null for disabling input service.
CompositionLocalProvider(
// You can also provides null to completely disable the default input service.
LocalTextInputService provides myTextInputService
) {
BaseTextField(...)
}
You may see google employee answer here about this subject.
With ReadonlyTextField it is not possible to position the cursor. So added wrapped EditText inside a compose AndroidView
#Composable
fun NoKeyboardTextField(
modifier: Modifier,
text: String,
textColor: Int
) {
AndroidView(
modifier = modifier,
factory = { context ->
AppCompatEditText(context).apply {
isFocusable = true
isFocusableInTouchMode = true
showSoftInputOnFocus = false
}
},
update = { view ->
view.setText(text)
view.setTextColor(textColor)
view.setSelection(text.length)
}
)
}

Categories

Resources