So i'm implementing a TextField with TransformedText to generate formatting for a phone number input. It works great for the 'happy path'. When the phone number is entered, you can go out of and back into focus on the TextField with no issue.
However, if I were to half fill in the field, as below, go out of focus and go back into focus the app will crash
The error message in the StackTrace is as follows:
java.lang.IllegalStateException: OffsetMapping.transformedToOriginal returned invalid mapping: 15 -> 10 is not in range of original text [0, 5]
So i'm aware the issue is with the number of characters I inserted into the TextField in this example (5), i'm just not sure how to catch this refocus to stop it crashing when I go back into focus, below is the code for the TextField and the TransformedText formatter
TextField
@Composable
private fun DisplayNumberTextField() {
var num by remember { mutableStateOf("") }
OutlinedTextField(
value = num,
onValueChange = {
num = it
},
modifier = Modifier
.fillMaxWidth(),
singleLine = true,
visualTransformation = {
if (it.isBlank()) {
TransformedText(it, OffsetMapping.Identity)
} else {
phoneNumberInputFormatter(it)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
)
}
Formatter
private fun phoneNumberInputFormatter(text: AnnotatedString): TransformedText {
val mask = "+1 xxx-xxx-xxxx"
val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text
val annotatedString = AnnotatedString.Builder().run {
for (i in trimmed.indices) {
if (i == 0) {
append("+1 ")
}
append(trimmed[i])
if (i == 2 || i == 5) {
append("-")
}
}
pushStyle(SpanStyle(color = Color.LightGray))
append(mask.takeLast(mask.length - length))
toAnnotatedString()
}
val phoneNumberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 0) return offset + 2
if (offset <= 3) return offset + 3
if (offset <= 7) return offset + 4
if (offset <= 10) return offset + 5
return 15
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 0) return offset - 2
if (offset <= 3) return offset - 3
if (offset <= 7) return offset - 4
if (offset <= 10) return offset - 5
return 10
}
}
return TransformedText(annotatedString, phoneNumberOffsetTranslator)
}
I've come to my solution from various SO threads, for example as well as other sources online, for example , and can't seem to see how to fix the OffsetTranslator to avoid this crash.
Any help would be much appreciated
Change OffsetMapping
to this,
val phoneNumberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return when {
offset == 0 -> return 0
offset <= 3 -> offset + 3
offset <= 6 -> offset + 4
else -> offset + 5
}
}
override fun transformedToOriginal(offset: Int): Int {
return text.length
}
}