b

    Bradleycorn

    1 year ago
    Question about
    TextField
    value and State. Can someone explain why, given the following composable:
    var text by remember { mutableStateOf("") }
    TextField(
        value = text,
        onValueChange = { newText ->
            Log.d("TEXTFIELD", "NewText: $newText")
            val editedText = newText.replace(Regex("\\D"), "")
            if (editedText.startsWith("1"))
                text = editedText
        }
    )
    If I type the number 5 into the text field four times, the result I see in LogCat is:
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 55
    D/TEXTFIELD: NewText: 5
    I’m stumped?
    But, If I type 5 4 5 4, the result in LogCat is:
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 4
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 4
    Stylianos Gakis

    Stylianos Gakis

    1 year ago
    Does it happen consistently no matter how fast or slow you type it?
    b

    Bradleycorn

    1 year ago
    it’s actually a repetitive pattern. as long as you type the same character, it will repeat itself every 3rd time… For example:
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 55
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 55
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 55
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 55
    Or, if you type 5 5 4, it’ll output:
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 5
    D/TEXTFIELD: NewText: 54
    Sean McQuillan [G]

    Sean McQuillan [G]

    1 year ago
    I'm going to blame code gremlins
    But really no actual theories for why it's reproing this exact way. The internal state story of TextField is somewhat complex due to the way ime works. Can you file a bug with minimal repro in https://issuetracker.google.com/components/779818
    It may be indicative of something else and is worth investigating further
    b

    Bradleycorn

    1 year ago
    @Sean McQuillan [G] sure thing. I felt like it has to be a defect of some kind. I’ll file it.
    Sean McQuillan [G]

    Sean McQuillan [G]

    1 year ago
    Thanks!
    b

    Bradleycorn

    1 year ago
    Rick Regan

    Rick Regan

    11 months ago
    Is there a workaround for this? I have the same problem (presumably). I'm trying to edit the input (
    KeyboardType.Number
    ) to remove leading 0s but they keep accumulating. (The issue above has 10 stars but is still marked 'new'.)
    Sean McQuillan [G]

    Sean McQuillan [G]

    11 months ago
    I haven't had a chance to take a look yet. For the case of leading zeroes, you should be able to transform that to trim all leading zeroes and it'll get reset. As a model, ime doesn't actually type one char at a time as the only input (it can also replace, append a word, etc), so you should expect the next string to be potentially a large edit from the previous.
    Rick Regan

    Rick Regan

    11 months ago
    I was worried that I was going to get some unpredictable value that I had to handle because of this bug. But I guess the reliable workaround (or even what I think I just learned is what I should be doing anyway) is to reparse
    it
    every time. I was initially stripping off the first leading 0 thinking
    it
    could never then be more than one 0 (I had assumed
    it
    was
    value
    plus the new character). Instead now I do this (I also remove any non-numeric character from the
    Number
    keyboard) and it seems to work:
    value = text,
    onValueChange = {
        text = it.replace(Regex("^0*"),"")
        text = text.replace(Regex("[ ]"),"")
        text = text.replace(Regex("[-]"),"")
        text = text.replace(Regex("[,]"),"")
        text = text.replace(Regex("[.]"),"")
        println("it = \"$it\", text = \"$text\"")
    },
    The value if
    it
    is still curious though, as per the bug I guess. It is as I expect except for leading 0s -- it retains every leading 0 typed. For example, after typing five 0s, this prints it = "00000", text = "". Typing another digit after that (say 9) it prints it = "000009", text = "9". Typing yet another digit (say 8) it prints it = "98", text = "98".
    text
    is still correct, which is what really matters I guess. Thanks for clearing this up for me (now I know why it's low severity, despite the interest).
    Sean McQuillan [G]

    Sean McQuillan [G]

    11 months ago
    Rick, I want to put a quick note in docs on this because I'm sure you're not the last person who will run across this and get confused. Did you look at this page? https://developer.android.com/jetpack/compose/text
    Rick Regan

    Rick Regan

    11 months ago
    Thanks. I've been on that page a number of times, including yesterday 🙂. But I didn't see anything other than "value = it" examples (there or elsewhere I searched).
    Sean McQuillan [G]

    Sean McQuillan [G]

    11 months ago
    Yep, I just wrote up a quick section and sent it off to review
    sadly that doesn't go aosp so can't link

    Cleaning input

    A common task when editing text is to strip leading characters, or otherwise transform the input string each character. As a model, you should assume that the keyboard may make aribitrary and large edits each
    onValueChange
    . This may happen, for example, if the user uses autocorrect, replaces a word with an emoji, or other smart editing features. To correctly handle this, write any transformation logic with the assumption that the current text passed to
    onValueChange
    is unrelated to the previous or next values that will be passed to
    onValueChange
    . To implement a text field that disallows leading zeros, you can do this by stripping all leading zeroes on every value change.
    kotlin
    @Composable
    fun NoLeadingZeroes() {
      val input by rememberSavable { mutableStateOf("") }
      TextField(
          value = input
          onValueChange = { newText ->
              text = newText.trimStart { it == '0' }
          }
      )
    }
    To control the cursor position while cleaning text, use the
    TextFieldValue
    overload of
    TextField
    instead of passing a regular string. This overload allows you to change the cursor position as part of the state.
    thanks for confirming this is the right page, and thanks for feedback on model!
    Rick Regan

    Rick Regan

    11 months ago
    That will be super helpful. (BTW, did you mean input = newText.trimStart ... in the lambda?)
    Sean McQuillan [G]

    Sean McQuillan [G]

    11 months ago
    I did 😄 thanks for review
    b

    Bradleycorn

    10 months ago
    @Sean McQuillan [G] I filed another issue on the tracker that is different but seems related. It’s also odd in that I can only reproduce it on a Samsung Galaxy S10 (and not on a Pixel or an Emulator). https://issuetracker.google.com/issues/206656075 Essentially with this composable:
    @Composable
    fun LimitedTextField(maxLength: Int) {
        var text by remember { mutableStateOf("") }
        TextField(value = text, onValueChange = { new ->
            val filteredText = new.take(maxLength)
            text = filteredText
        })
    }
    If you call it with
    maxLength = 5
    , and then type “abcdefg” it will show “g”, instead of “abcde”.
    Sean McQuillan [G]

    Sean McQuillan [G]

    10 months ago
    Thanks for that minimal repro!
    Rick Regan

    Rick Regan

    10 months ago
    Isn't the new issue (206656075) an actual bug (because
    value
    is incorrect) vs. the original one (200577798) where there was just curious "suggested text"? (I'm just trying to understand if/why they are different.)
    b

    Bradleycorn

    10 months ago
    @Rick Regan The reason I mentioned the 2nd one here and suggested it might be related to the original is because in both cases the value passed to the
    onValueChange
    callback is not correct. So it’s possible that whatever is calculating the value that gets passed to
    onValueChange
    has a defect, and when that is fixed, it could conceivably fix both of these issues
    Rick Regan

    Rick Regan

    10 months ago
    I definitely appreciate you posting that here to keep us updated. I just wanted to know if I should be worried about a defect lurking beyond Galaxy S10s. (After the original discussion my takeaway was all I had to do to avoid it was to reparse the input every time...but now I'm not sure if that covers it.)
    b

    Bradleycorn

    10 months ago
    yeah, it is odd that the 2nd one can only be reproduced on specific devices. Not sure why that is.