Reputation: 14246
I have a TextField
on one of my app screens. When I rotate my device the text field retains the value, but not the soft keyboard focus.
How could I keep the focus and prevent the keyboard from disappearing?
Here is a simplified version of the composable for the screen:
@Composable
fun LoginScreen(
uiState: LoginUiState,
) {
MyTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollableState)
.imePadding(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = uiState.email,
enabled = !uiState.isLoggingIn
)
}
}
}
}
The UI state comes from the model.
Upvotes: 4
Views: 1513
Reputation: 10619
You can use this code which use focusRequester to change the focus after orientation:
val focusRequester = remember { FocusRequester() }
var isFirstComposition by rememberSaveable { mutableStateOf(true) }
TextField(
value = ...,
onValueChange = { ... },
modifier = Modifier.focusRequester(focusRequester)
)
LaunchedEffect(focusRequester) {
if (!isFirstComposition) {
focusRequester.requestFocus()
}
isFirstComposition = false
}
Upvotes: -1
Reputation: 6947
You can also set this in your activity manifest:
<activity
android:name=".MyActivity"
android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
In the Compose world, you don't need to destroy your activity. On configuration change, you will see that the composables recompose. This above fixes the issue with retaining the keyboard state.
Upvotes: 0
Reputation: 96
I believe this is related to this issue about TextField losing focus on configuration change .
A possible temporary solution is to save focus boolean changes from modifier onFocusChanged
into a variable using rememberSaveable
, then use a LaunchedEffect
to restore focus to the TextField
when it enters the composition.
val focusRequester = remember { FocusRequester() }
var isFocused by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(true) {
if (isFocused) {
focusRequester.requestFocus()
}
}
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged {
isFocused = it.isFocused
}
)
However, this solution doesn't seem to work because when the TextField
enters the composition, onFocusChanged
is called right away and updates isFocused
to false
before the LaunchedEffect
has a chance to process the previous value.
Therefore, an additional variable can be used to store the value from isFocused
before it's updated. Below I name it wasFocused
:
val focusRequester = remember { FocusRequester() }
var isFocused by rememberSaveable { mutableStateOf(false) }
val wasFocused = remember { isFocused }
LaunchedEffect(true) {
if (wasFocused) {
focusRequester.requestFocus()
}
}
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged {
isFocused = it.isFocused
}
)
This works in my android emulator after rotating the device. Let me know if it doesn't restore the focus for you on your device.
(I should mention that this solution is for a simple case. An issue may arise when used in a more complex case, such as many TextField
s in something like a LazyColumn
. I do have such a case, and use this as part of my solution).
Upvotes: 1
Reputation: 50538
You need to use rememberSaveable
to store wither the TextField
was focused previously.
val focusRequester = remember { FocusRequester() }
var hasFocus by rememberSaveable { mutableStateOf(false) }
TextField(
value = ...,
onValueChange = { ... },
modifier = Modifier.focusRequester(focusRequester).onFocusChanged {
hasFocus = it.hasFocus
}
)
LaunchedEffect(hasFocus){
if(hasFocus) {
focusRequester.requestFocus()
}
}
Upvotes: 1
Reputation: 6835
You are using the a state-preserver like a ViewModel here, I suppose. You could either store the value in a rememberSaveable
block, as Nikola suggests, or you could simply put a simple Boolean where you put the uiState parameter. There's no need to use MutableState<T>
this way. Also, no side-effects are required. Just create a parameter.
@Composable
fun MyFiled(
loginState: ... ,
isFocused: Boolean
){
if (isFocused)
focusRequestor.requestFocus()
...
}
Just put a simple condition, and it'll do.
Upvotes: 1