I'm trying to program something where a user inputs a number into an editText field and it automatically has decimal places appended. I'm using editText.setText
inside the listener, but find that the code crashes every time. Upon logging the value that I'm trying to edit, I found that the moment I change the editText field, editText.setText
runs non-stop, which causes the program to crash. Below is my code:
val etTesting = findViewById<EditText>(R.id.etTesting)
etTesting.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (!s.isNullOrEmpty() && s.toString() != ".") {
val value = s.toString().toDouble()
val formatted = String.format("%.2f", value)
Log.i(TAG, "onTextChanged $formatted")
etTesting.setText(formatted)
etTesting.setSelection(formatted.length)
}
}
})
I tried to use setText outside of the listener and it doesn't trigger a crash. It's only inside the listener. I'm guessing it has to do with the listener writing an updated value, seeing that it's updated again, and then goes through this sequence infinitely?
How is one supposed to use the listener to do what I'm trying to do?
Thanks for the help.
As a more general approach (instead of relying on filters, which aren't always enough for what you want to do) you could create some kind of updating
flag in your TextWatcher
:
object : TextWatcher {
var updating = false
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// skip reacting to the text change if it's caused by an internal update
if (!updating) {
updating = true
// make changes to the text contents
updating = false
}
}
}
Basically when onTextChanged
fires, it checks to see if there's an internal update happening, and only proceeds if there isn't. First it sets that updating
flag, and then updates the text, which immediately triggers the TextWatcher
callbacks again.
Those calls are recursive, so your initial onTextChanged
function won't complete until the triggered onTextChanged
returns. And if that fires another onTextChanged
, you end up with infinite nested function calls until you get a (heyo) stack overflow.
By using this flag, you short-circuit that behaviour. Because the first call sets that updating
flag, the text change will trigger a second call, which sees that updating
is true, and returns without making any changes. Then your first onTextChanged
call will complete. And the last thing it does is set updating
to false since the update process has finished, so it's ready to handle the next change.