Home > OS >  Android Jetpack Compose: Keyboard changing from numeric to alphabets after modifying input text
Android Jetpack Compose: Keyboard changing from numeric to alphabets after modifying input text

Time:12-24

While exploring TextField in a Jetpack Compose, I came across a case where I have to modify input typed in the field. For example, adding a comma after entering 3 characters.

This is how I made it.

@Composable
fun TFDemo() {
    var fieldValue by remember { mutableStateOf(TextFieldValue("")) }

    TextField(
        value = fieldValue,
        onValueChange = {
            val newMessage = it.text.let { text -> if (text.length == 3) "$text," else text }
            fieldValue = it.copy(newMessage, selection = TextRange(newMessage.length))
        },
        keyboardOptions = KeyboardOptions(autoCorrect = false),
    )
}

But after running it, I realized that after the comma is added, keyboard view changed back to alphabets from numbers/symbols which should not be the case. See video output below for clarity

As you can see in the below video when I typed "111" comma was appended and suddenly keyboard's numeric view changed to alphabets again.

Jetpack Compose: Keyboard changing issue


Here I have modified the selection of TextFieldValue so that cursor always be at the end of the message whenever a comma is appended.

CodePudding user response:

This kind of cases is exactly what VisualTransformation is intended for.

Here's a Googler's comment on another issue:

I don't think we can fix this issue easily.

The filtering text in onValueChanged callback is generally not recommended because the text state is shared with out process IME(software keyboard). The filtering text means the text content changes internally, then the new state is notified to IME. This is not a normal path to IME and different IME reacts differently to this unexpected state change. Some IME may try to reconstruct the composition, others may give up and start new session, etc. This is mostly due of the historical reason and hard to fix from now. So, please avoid filtering text in onValueChanged callback and consider following alternatives:

  1. (Recommended) Don't filter it and show error message. (irrelevant here)
  2. Use VisualTransformation for changing visual output without modifying edit buffer.

CodePudding user response:

As per the mentioned answer above, VisualTransformation is the perfect solution for such cases and we should not directly modify TextField's buffer. Because VisualTransformation just changes the visual output of text and not actual text.

I've written an article on this scenario here where I've explained this in detail.

Solution:

@Composable
fun TextFieldDemo() {
    var message by remember { mutableStateOf("") }

    TextField(
        value = message,
        placeholder = { Text("Enter amount or message") },
        onValueChange = { message = it },
        visualTransformation = AmountOrMessageVisualTransformation()
    )
}

class AmountOrMessageVisualTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {

        val originalText = text.text
        val formattedText = formatAmountOrMessage(text.text)

        val offsetMapping = object : OffsetMapping {

            override fun originalToTransformed(offset: Int): Int {
                if (originalText.isValidFormattableAmount) {
                    val commas = formattedText.count { it == ',' }
                    return when {
                        offset <= 1 -> offset
                        offset <= 3 -> if (commas >= 1) offset   1 else offset
                        offset <= 5 -> if (commas == 2) offset   2 else offset   1
                        else -> 8
                    }
                }
                return offset
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (originalText.isValidFormattableAmount) {
                    val commas = formattedText.count { it == ',' }
                    return when (offset) {
                        8, 7 -> offset - 2
                        6 -> if (commas == 1) 5 else 4
                        5 -> if (commas == 1) 4 else if (commas == 2) 3 else offset
                        4, 3 -> if (commas >= 1) offset - 1 else offset
                        2 -> if (commas == 2) 1 else offset
                        else -> offset
                    }
                }
                return offset
            }
        }

        return TransformedText(
            text = AnnotatedString(formattedText),
            offsetMapping = offsetMapping
        )
    }
}
  • Related