Bobrovsky
Bobrovsky

Reputation: 14246

Restore keyboard after rotation in Jetpack Compose

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

Answers (5)

AliSh
AliSh

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

VIN
VIN

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

Joel Alvarado
Joel Alvarado

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 TextFields in something like a LazyColumn. I do have such a case, and use this as part of my solution).

Upvotes: 1

Nikola Despotoski
Nikola Despotoski

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

Richard Onslow Roper
Richard Onslow Roper

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

Related Questions