How to add uneditable postfix (suffix) to TextField in Jetpack Compose?
L

4

7

How can I add a suffix to TextField input that flows (moves) with the user input text?

Lajuanalake answered 23/11, 2022 at 8:7 Comment(0)
A
14

With M3 starting from the 1.1.0-alpha06 you can use the suffix attribute:

    TextField(
        value = text,
        onValueChange = { text = it },
        suffix = { Text ("€") }
    )

enter image description here

Before M3 1.1.0-alpha06 or with M2 or you can use the visualTransformation attribute.

Something like:

TextField(
    value = text,
    onValueChange = { text = it },
    singleLine = true,
    visualTransformation = SuffixTransformation(" €"),
)

class SuffixTransformation(val suffix: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {

        val result = text + AnnotatedString(suffix)

        val textWithSuffixMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int): Int {
                return offset
            }

            override fun transformedToOriginal(offset: Int): Int {
                if (text.isEmpty()) return 0
                if (offset >=  text.length) return text.length                    return offset
            }
        }

        return TransformedText(result, textWithSuffixMapping )
    }
}

enter image description here

If you have the placeholder you can put a condition in the visualTransformation attribute.

Something like:

TextField(
    value = text,
    onValueChange = { text = it },
    singleLine = true,
    visualTransformation = if (text.isEmpty())
        VisualTransformation.None
    else
        SuffixTransformation(" €"),
    placeholder = { Text("Placeholder") }
)
Apocynaceous answered 23/11, 2022 at 9:8 Comment(6)
Thanks for the answer. But this was a self-answered question. I think you didn't notice that :) What do you think of my answer? Actually, I derived it from one of your posts!Lajuanalake
Thank you for your answer, very useful 👍 (just check the suggestion of Martynas B, it's right.) If it can be useful to someone, I use this code to avoid overlapping when using BasicTextField with placeholder : ` var suffixLocal: String = "" // avoid to overlap placeholder if (!text.isEmpty()) suffixLocal = suffix val result:AnnotatedString = text + AnnotatedString(suffixLocal) val suffixOffset = suffixLocal.length `Showers
@Showers thanks for the feedback. I've update the answer with the placeholder scenario. I would prefer to put a condition in the visualTransformation attribute just to have more flexibility. For example you can also handle the focus state with a placeholder.Apocynaceous
what is M3 and im not able to see any suffix property i can see only trailingIcon which accepts a compose objectCroquette
@Croquette developer.android.com/jetpack/androidx/releases/…Apocynaceous
@GabrieleMariotti how can I do this? when I try I'm able to see only trailingIcon and its adding some padding and looks weird for my suffix iconCroquette
A
4

I found Gabriele Mariotti's answer buggy. Needed to change transformedToOriginal function to this:

override fun transformedToOriginal(offset: Int): Int {
    if (offset > text.length) return text.length
    return offset
}
Aryn answered 11/1, 2023 at 15:25 Comment(1)
it's right, without this correction it gives errors like this, in my case happens with 2 fields, moving cursor by clicking from one to another o something else: java.lang.IllegalStateException: OffsetMapping.transformedToOriginal returned invalid mapping: 5 -> 5 is not in range of original text [0, 4]Showers
L
1

This is easily done in Compose like this:

const val SUFFIX = " $"
@Composable
fun SuffixedText() {
    var text by remember { mutableStateOf("") }
    TextField(
        text,
        singleLine = true,
        visualTransformation = SuffixTransformer(SUFFIX),
        onValueChange = { text = it }
    )
}
class SuffixTransformer(val suffix: String) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        val result = text + AnnotatedString(suffix)
        return TransformedText(result, OffsetMapping.Identity)
    }
}

The above component probably can be used in traditional Views too. See this post

Also, see the following:

Lajuanalake answered 23/11, 2022 at 8:7 Comment(2)
It has some issues. For example if you click in the suffix (inside o just after the last character) the code crashed because of OffsetMapping.IdentityApocynaceous
I tied it in a Desktop app using Compose Multiplatform v1.2.1 which uses Compose v??? on Windows and did not encounter any error. Maybe it's a regression in newer versions of Compose?Lajuanalake
Y
0

OffsetMapping.identity cause crash on Android. You should add the offset mapping.

class PreSuffixTransformer(val prefix: String = "", val suffix: String = "") : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        val result = AnnotatedString(prefix) + text + AnnotatedString(suffix)

        val offsetMapping = object : OffsetMapping {
            override fun originalToTransformed(offset: Int) =
                offset + prefix.length
            override fun transformedToOriginal(offset: Int) =
                if(text.length < offset){
                    text.length
                } else if(offset < prefix.length){
                    0
                }else {
                    offset - prefix.length
                }
        }
        return TransformedText(result, offsetMapping)
    }
}
Yates answered 12/5 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.