Jetpack compose single number text field

Viewed 4201

I am trying to create a phone verification screen where the user must enter 5 numbers each in their own text field like below. Verification text field

I have two questions:

  1. Is there a way to limit a TextField to 1 character. I can set single line and max lines, but don't see a way to limit to character length like 'Ms' from the view system. I can easily limit the character length in code by ignoring characters after the first one, but this still lets the user 'scroll' to the left and right even though there is only 1 character.
  2. Is there a way to wrap the width to the 1 character? Currently the only way I have found to limit the width is to set it specifically, but then if the system text size is changed it could break.

Here is some code incase it helps, this is some very jumbled together solution so apologies if something is incorrect:

@Composable
fun CodeTextFields(
    modifier: Modifier = Modifier,
    length: Int = 5,
    onFilled: (code: String) -> Unit
) {
    var code: List<Char> by remember {
        mutableStateOf(listOf())
    }
    val focusRequesters: List<FocusRequester> = remember {
        val temp = mutableListOf<FocusRequester>()
        repeat(length) {
            temp.add(FocusRequester())
        }
        temp
    }

    Row(modifier = modifier) {
        (0 until length).forEach { index ->
            OutlinedTextField(
                modifier = Modifier
                    .weight(1f)
                    .padding(vertical = 2.dp)
                    .focusRequester(focusRequesters[index]),
                textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
                singleLine = true,
                value = code.getOrNull(index)?.takeIf { it.isDigit() }?.toString() ?: "",
                onValueChange = { value: String ->
                    if (focusRequesters[index].freeFocus()) {   //For some reason this fixes the issue of focusrequestor causing on value changed to call twice
                        val temp = code.toMutableList()
                        if (value == "") {
                            if (temp.size > index) {
                                temp.removeAt(index)
                                code = temp
                                focusRequesters.getOrNull(index - 1)?.requestFocus()
                            }
                        } else {
                            if (code.size > index) {
                                temp[index] = value.getOrNull(0) ?: ' '
                            } else if (value.getOrNull(0)?.isDigit() == true) {
                                temp.add(value.getOrNull(0) ?: ' ')
                                code = temp
                                focusRequesters.getOrNull(index + 1)?.requestFocus() ?: onFilled(
                                    code.joinToString(separator = "")
                                )
                            }
                        }
                    }
                },
                keyboardOptions = KeyboardOptions.Default.copy(
                    keyboardType = KeyboardType.Number,
                    imeAction = ImeAction.Next
                ),

                )
            Spacer(modifier = Modifier.width(16.dp))
        }
    }
}
3 Answers

To limit to 1 number you can use something like:

@Composable
fun Field (modifier: Modifier = Modifier,
      onValueChange: (String, String) -> String = { _, new -> new }){

    val state = rememberSaveable { mutableStateOf("") }

    OutlinedTextField(
        modifier = modifier.requiredWidth(75.dp),
        singleLine = true,
        value = state.value,
        onValueChange = {
            val value = onValueChange(state.value, it)
            state.value = value
        },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Number,
            imeAction = ImeAction.Next),
        )
}

and then use:

Field(onValueChange = { old, new ->
    if (new.length > 1 || new.any { !it.isDigit() }) old else new
})

enter image description here

set your keyboard option number password type see the below code

keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)

below the keyboard output

enter image description here

To solve this usecase you can use decoration box in BasicTextfield.

@Composable
fun InputField(
    modifier: Modifier = Modifier,
    text: String = "",
    length: Int = 5,
    keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
    onFocusChanged: () -> Unit = {},
    onTextChange: (String) -> Unit
) {
    val BoxHeight = 40.dp
    val BoxWidth = 40.dp
    var textValue by remember { mutableStateOf(text) }
    val focusRequester = remember { FocusRequester() }
    val focusManager = LocalFocusManager.current
    var isFocused by remember { mutableStateOf(false) }

    if (text.length == length) {
        textValue = text
    }

    BasicTextField(
        modifier = modifier
            .focusRequester(focusRequester)
            .onFocusChanged {
                isFocused = it.isFocused
                onFocusChanged(it)
            },
        value = textValue,
        singleLine = true,
        onValueChange = {
            textValue = it
            if (it.length <= length) {
                onTextChange.invoke(it)
            }
        },
        enabled = enabled,
        keyboardOptions = keyboardOptions,
        decorationBox = {
            Row(Modifier.fillMaxWidth()) {
                repeat(length) { index ->
                    Text(
                        text = textValue,
                        modifier = Modifier
                            .size(
                                width = 
                        BoxWidth,
                                height = BoxHeight
                            )
                            .clip(RoundedCornerShape(4.dp))
                            ,
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                }
            }
        }
    )

    if (acquireFocus && textValue.length != length) {
        focusRequester.requestFocus()
    }
}

This will create a boxes as shown below length number of times(5 here). This is just a simple TextField with decoration as multiple boxes. You can use this text in viewModel.

TextField Examples: https://developer.android.com/jetpack/compose/text

here][1]

Related