https://kotlinlang.org logo
b

Bradleycorn

09/20/2021, 7:47 PM
Question about
TextField
value and State. Can someone explain why, given the following composable:
Copy code
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:
Copy code
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 55
D/TEXTFIELD: NewText: 5
I’m stumped?
🧵 1
But, If I type 5 4 5 4, the result in LogCat is:
Copy code
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 4
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 4
s

Stylianos Gakis

09/20/2021, 8:40 PM
Does it happen consistently no matter how fast or slow you type it?
b

Bradleycorn

09/20/2021, 8:41 PM
yes
😨 2
it’s actually a repetitive pattern. as long as you type the same character, it will repeat itself every 3rd time… For example:
Copy code
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:
Copy code
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 5
D/TEXTFIELD: NewText: 54
s

Sean McQuillan [G]

09/20/2021, 9:56 PM
I'm going to blame code gremlins
❤️ 2
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

09/20/2021, 9:59 PM
@Sean McQuillan [G] sure thing. I felt like it has to be a defect of some kind. I’ll file it.
s

Sean McQuillan [G]

09/20/2021, 9:59 PM
Thanks!
b

Bradleycorn

09/21/2021, 12:18 AM
@Sean McQuillan [G] - Issue filed! https://issuetracker.google.com/issues/200577798
👏 3
👏🏼 1
r

Rick Regan

09/29/2021, 2:01 AM
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'.)
s

Sean McQuillan [G]

09/29/2021, 2:55 AM
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.
r

Rick Regan

09/29/2021, 1:10 PM
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:
Copy code
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).
s

Sean McQuillan [G]

09/29/2021, 4:28 PM
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
r

Rick Regan

09/29/2021, 4:54 PM
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).
s

Sean McQuillan [G]

09/29/2021, 4:54 PM
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.
Copy code
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.
👏 1
thanks for confirming this is the right page, and thanks for feedback on model!
r

Rick Regan

09/29/2021, 4:58 PM
That will be super helpful. (BTW, did you mean input = newText.trimStart ... in the lambda?)
s

Sean McQuillan [G]

09/29/2021, 5:41 PM
I did 😄 thanks for review
b

Bradleycorn

11/17/2021, 8:31 PM
@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:
Copy code
@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”.
🎉 1
s

Sean McQuillan [G]

11/18/2021, 7:36 PM
Thanks for that minimal repro!
r

Rick Regan

11/19/2021, 4:51 PM
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

11/19/2021, 4:56 PM
@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
r

Rick Regan

11/19/2021, 5:08 PM
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

11/19/2021, 5:11 PM
yeah, it is odd that the 2nd one can only be reproduced on specific devices. Not sure why that is.
6 Views