Jetpack compose: Setting ImeAction does not close or change focus for the keyboard

Viewed 12352

I'm using Jetpack compose 1.0.0-alpha07. I made a login screen that contains two TextFields customized using other composables.

However, setting ImeAction in keyboardOptions does not seem to work. For instance ImeAction.Next does not move focus to the next TextField. I think I should do something to make it possible, but no document or article has talked even briefly about ImeOptions. Here's the code I have for the screen:

Login composable:

EmailEdit(onChange = { email.value = it })
PasswordEdit(onChange = { password.value = it })

EmailEdit:

@Composable
fun EmailEdit(onChange: (String) -> Unit) {
    val t = remember { mutableStateOf("") }
    TextField(
        value = t.value,
        onValueChange = { value ->
            t.value = value
            onChange(value)
        },
        leadingIcon = { Icon(asset = Icons.Default.Email) },
        label = { Text(text = "Email") },
        maxLines = 1,
        keyboardOptions = KeyboardOptions(
            imeAction = ImeAction.Next, // ** Go to next **
            keyboardType = KeyboardType.Email
        ),
        visualTransformation = VisualTransformation.None
    )
}      errorHint = "Not a valid email"
    )
}

PassEdit:

@Composable
fun PasswordEdit(onChange: (String) -> Unit) {
    val t = remember { mutableStateOf("") }
    TextField(
        value = t.value,
        onValueChange = { value ->
            t.value = value
            onChange(value)
        },
        leadingIcon = { Icon(asset = Icons.Default.Security) },
        label = { Text(text = "Password") },
        maxLines = 1,
        keyboardOptions = KeyboardOptions(
            imeAction = ImeAction.Done, // ** Done. Close the keyboard **
            keyboardType = KeyboardType.Text
        ),
        visualTransformation = PasswordVisualTransformation()
    )
}

To perform Done and Next what code should I add?

4 Answers

You can use

  • keyboardOptions: software keyboard options that contains configuration such as KeyboardType and ImeAction
  • keyboardActions when the input service emits an IME action, the corresponding callback is called

For Done:

You can use the LocalSoftwareKeyboardController to interact with the keyboard.

keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
    onDone = {keyboardController?.hide()}
)

For Next:

keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(
    onNext = { focusRequester.requestFocus() }
)

Something like:

val (focusRequester) = FocusRequester.createRefs()
val keyboardController = LocalSoftwareKeyboardController.current

TextField(
    value = text,
    onValueChange = {
        text = it
    },
    label = { Text("Label") },
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
    keyboardActions = KeyboardActions(
        onNext = { focusRequester.requestFocus() } 
    )
)

TextField(
    value = text,
    onValueChange = {
        text = it
    },
    modifier = Modifier.focusRequester(focusRequester),
    label = { Text("Label") },
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
    keyboardActions = KeyboardActions(
        onDone = { keyboardController?.hide() }
    )
)

You can use LocalFocusManager.

val localFocusManager = LocalFocusManager.current

Inside parent composable of your fields.

To move focus to next field:

localFocusManager.moveFocus(FocusDirection.Down)

Inside onNext of KeyboardActions to move focus in specific direction such as left, right, up and down.

To clear focus:

localFocusManager.clearFocus()

Inside onDone of KeyboardActions to clear focus.

Email Field :

OutlinedTextField(
            value = userId,
            onValueChange = { userId = it },
            label = { Text("Email") },
            modifier = Modifier.fillMaxWidth(),
            singleLine = true,
            leadingIcon = {
                Icon(
                    painter = painterResource(id = R.drawable.ic_account),
                    contentDescription = "ID"
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                focusedBorderColor = Color.Gray,
                unfocusedBorderColor = Color.LightGray,
                focusedLabelColor = Color(0xffcc0000)
            ),
            keyboardOptions =
            KeyboardOptions(
                keyboardType = KeyboardType.Text,
                imeAction = ImeAction.Next
            ),
            keyboardActions = KeyboardActions(onNext = {
                localFocusManager.moveFocus(FocusDirection.Down)
            })
        )

Password Field :

OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("Password") },
            modifier = Modifier.fillMaxWidth(),
            singleLine = true,
            leadingIcon = {
                Icon(
                    painter = painterResource(id = R.drawable.ic_password),
                    contentDescription = "Password"
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                focusedBorderColor = Color.Gray,
                unfocusedBorderColor = Color.LightGray,
                focusedLabelColor = Color(0xffcc0000)
            ),
            keyboardOptions =
            KeyboardOptions(
                keyboardType = KeyboardType.Password,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(onDone = {
                localFocusManager.clearFocus()
            })

        )

Tried with version 1.0.1

use the onImeActionPerformed parameter.

For Done:

TextField(
    onImeActionPerformed = { _, controller ->
        controller?.hideSoftwareKeyboard()
    }
)

For Next:

val focusRequester = remember { FocusRequester() }
TextField(
    onImeActionPerformed = { _, _ ->
        focusRequester.requestFocus()
    }
)
TextField(
    modifier = Mofifier.focusRequester(focusRequester)
)

Here's a working example:

val focusRequester = remember { FocusRequester() }
val email = remember { mutableStateOf("") }
TextField(
    value = email.value,
    onValueChange = { email.value = it },
    imeAction = ImeAction.Next,
   onImeActionPerformed = { _, _ -> focusRequester.requestFocus() }
)
val password = remember { mutableStateOf("") }
TextField(
    value = password.value,
    onValueChange = { password.value = it },
    imeAction = ImeAction.Done,
    onImeActionPerformed = { _, controller -> controller?.hideSoftwareKeyboard() },
    modifier = Modifier.focusRequester(focusRequester)
)

Documentation:

With Compose in this instance you need to create a set of references to each focusable text field on your screen as below (textField1, textField2) and get a reference to the keyboardController.

Then you can add a type of action to your keyboard options signalling how the action button should look when the keyboard is displayed.

In the keyboardActions parameter, you can call a function on the specified IME action - in this case i've stated I want textField2 to take focus when the keyboard action button is pressed. You assign the references in the Modifier.focusRequester parameter in the TextField.

Lastly, to make your first TextField take focus when the screen appears, you call a DisposableEffect function where you specify you would like textField1 to take focus when the screen is first displayed.

val (textField1, textField2) = remember { FocusRequester.createRefs() }
val keyboardController = LocalSoftwareKeyboardController.current

TextField(
    modifier = Modifier.focusRequester(textField1),
    keyboardOptions  = KeyboardOptions(imeAction = ImeAction.Next),
    keyboardActions = KeyboardActions( onNext = {textField2.requestFocus()}
   ),
)


 TextField(
    modifier = Modifier.focusRequester(textField2),
    keyboardOptions  = KeyboardOptions(imeAction = ImeAction.Done),
    keyboardActions = KeyboardActions( onDone = { 
    keyboardController?.hide()}
   ),
)

DisposableEffect(Unit) {
    textField1.requestFocus()
    onDispose { }
}
Related