38

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?

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Mahdi-Malv
  • 16,677
  • 10
  • 70
  • 117

4 Answers4

60

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() }
    )
)
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • 5
    Weird, I'm getting exception when trying request focus in this way: `java.lang.IllegalStateException: FocusRequester is not initialized. One reason for this is that you requesting focus changes during composition. Focus requesters should not be made during composition, but should be made in response to some event.` – Yupi May 27 '21 at 04:29
  • 2
    add Modifier.focusRequester(focusRequester) to solve the IllegalStateException – bluevoid Jul 07 '21 at 09:51
  • 2
    Strange, keyboard closes on pressing onNext in Landscape and working fine in portrait mode. – Jatin Sachdeva Aug 12 '21 at 14:30
  • 1
    LocalSoftwareKeyboardController is still experimental, should we use it in production yet? – Amit Sep 16 '21 at 01:20
  • 3
    With Compose 1.1.1 it seems `keyboardActions` and manual focus management are not required. `KeyboardOptions(imeAction = ImeAction.Next)` is enough to get correct behavior. – gmk57 Mar 09 '22 at 15:53
27

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

Jatin Sachdeva
  • 1,091
  • 11
  • 12
15

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:

Noah
  • 2,718
  • 3
  • 17
  • 23
7

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 { }
}
alfietap
  • 1,585
  • 1
  • 15
  • 38
  • 1
    This seems like the best approach, works with fields hidden behind a keyboard as well and gives control on where the focus should move to. – Bruce Wayne Aug 27 '21 at 07:42