Home > Enterprise >  Compose TextField shows wierd behavior
Compose TextField shows wierd behavior

Time:11-02

I am trying to implement a TextField which inputs an amount and formats it as soon as it is typed and also limits it to 100,000.

@Composable
fun MainScreen(
    viewModel: MyViewModel
) {
    val uiState by viewModel.uiState.collectAsState()
    Column {
        AmountSection(
            uiState.amount,
            viewModel::updateAmount
        )
        Text(text = viewModel.logs)
    }
}

@Composable
fun AmountSection(
    amount: TextFieldValue,
    updateAmount: (TextFieldValue) -> Unit
) {
    BasicTextField(
        value = amount,
        onValueChange = updateAmount,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number
    )
)

MyViewModel:

class MyViewModel: ViewModel() {

    private val _uiState = MutableStateFlow(MyUiState())
    val uiState: StateFlow<MyUiState> = _uiState

    var logs by mutableStateOf("")
    var text = ""

    fun updateAmount(amount: TextFieldValue) {
        val formattedAmount: String = amount.text.getFormattedAmount()
        text  = "input = ${amount.text}\n"
        text  = "output = $formattedAmount \n"
        logs = text
        _uiState.update {
            it.copy(amount = TextFieldValue(formattedAmount, TextRange(formattedAmount.length))
        }
    }    
}

data class MyUiState(val amount: TextFieldValue = TextFieldValue())

(logs and text are just for logging purpose. Was finding it difficult to share the logcat output so presented it this way)

Result:

enter image description here

  • When I press 6, the input is "12,3456" which is expected (ignore the currency)
  • My getFormattedAmount() function removes the last six as ( 123456 > 100000). It outputs "12,345" which is also correct. "12,345" is what gets displayed on the screen.
  • But when I press 7, I get the input "12,34567". Where did that 6 come from?? It was not in uiState.amount.

(Please ignore the last output line. getFormattedAmount only removes the last character if the amount exceeds the limit and it gave wrong output because it didn't expect that input)

I feel that I making some really silly mistake here and would be really thankful if somecome could help me find that out.

CodePudding user response:

Edit based on the edit:- From the looks of the question, it isn't much clear what you wish to achieve here, but this is my deduction - You just want a TextField that allows numbers to be input, but only up to a maximum value (VALUE, not characters). When a digit-press by a user leads to the value exceeding your max value, you want that digit to be not entered of course, but you wish to reflect no changes at all in this case, i.e., the field value should remain intact.

Based on the above deduction, here is an example:-

First of all, f your uiState variable. I'm keeping it simple for the sake of clarity.

class VM: ViewModel(){
 var fieldValue by mutableStateOf("")
 
 fun onFieldUpdate(newValue){
   if(newValue.toDouble() > 999999999999999)
   return
   else
   fieldValue = newValue
 }
}

@Composable
fun CrazyField(fieldValue: String, onFieldUpdate: (String) -> Unit){
 TextField(value = fieldValue, onValueChange = onFieldUpdate)
}

Do not comment further without actually running this.

Original answer:- Use a doubles parser.

var text by remember { mutableStateOf("") }
TextField(
    value = text,
    onValueChange = {
        if (it.toDouble() <= 100000)
            text = it //Based on your use-case - it won't cut off text or limit the amount of characters
       else text = it.subString(0,7) //In-case of pasting or fast typing
    },
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)

CodePudding user response:

Found this comment on Compose slack channel by Sean.

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.

So this is some issue with TextField & IME relationship.
I rewrote my getFormattedAmount function to format the given string without any assumptions (earlier it was assuming that amount is formatted till the last second character). Everything seems fixed now.

  • Related