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.
Here I have modified the selection
of TextFieldValue
so that cursor always be at the end of the message whenever a comma is appended.
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
)
}
}