1

I'm using EditText where I want to preset text or let user type something to it. I wanted to make it "all-caps" so I implemented capitalize function for my TextWatcher.

You can enter and leave screen with this EditText and TextWatcher multiple times go back and forth switching screens - that means I have to properly register and unregister TextWatcher from EditText upon screen enter/leave event.

What I noticed is, that if I enter and leave screen multiple times in a row, upon like 5th enter my whole app is frozen - so something is blocking UI Thread.

On deeper investigation I found out that if I call editText.setText("some string"), it will trigger my afterTextChanged listener multiple times. On 5th enter it triggered it like 50 times in a row and that caused UI Thread block.

How is this fixable or is it bug on Android side?

Code: Initializing screen on enter:

initEditTextListener()
App.log("FormScreen - setData")
formFirstEt?.setText(item.name)

private var inputWatcher: TextWatcher? = null
private fun initEditTextListener(){
        formFirstEt?.onEditorAction{
            formInputValidation(this.text.toString(), formFirstEt, App.getString("form_empty"), App.getString("form_empty_reason"))
            validateData()
            formSecondEt?.let { s -> if (s.isEnabled) s.requestFocus() }
        }

        inputWatcher = object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                App.log("FormScreen - initEditTextListener - afterTextChanged")
                validateData()
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                App.log("FormScreen - initEditTextListener - onTextChanged")
                validateData()
                formFirstEt?.let { et -> onFormFirstInputChanged(s.toString(), this, et)}
            }
        }

        inputWatcher?.let {formFirstEt?.addTextChangedListener(it)}
    }

All-Cap transformation function (should not trigger TextWatcher)

fun onFormFirstInputChanged(s: String, tv: TextWatcher, et: EditText) {
    et.removeTextChangedListener(tv)
    if (isFirstInputValid(s)) {
        with(et) {
            text.clear()
            append(s.toUpperCase(Locale.getDefault()))
            setSelection(s.length)
        }
    } else {
        val substring = s.substring(0, s.length - 1)
        with(et) {
            text.clear()
            append(substring.toUpperCase(Locale.getDefault()))
            setSelection(substring.length)
        }
    }
    et.addTextChangedListener(tv)
}

Clearing screen before leaving:

formFirstEt?.setText("")
inputWatcher?.let { formFirstEt?.removeTextChangedListener(it) }
inputWatcher = null

Logs:

-- First time screen enter ---
2021-01-14 11:24:50.324 1136-1136/? I/Project: FormScreen - setData
2021-01-14 11:24:50.458 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:50.460 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:52.838 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:52.841 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged

-- Second time screen enter --
2021-01-14 11:24:56.324 1136-1136/? I/Project: FormScreen - setData
2021-01-14 11:24:56.366 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.367 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.368 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:56.368 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.369 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:56.369 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.370 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.370 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:56.371 1136-1136/? I/Project: FormScreen - initEditTextListener - onTextChanged
2021-01-14 11:24:56.372 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:56.372 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged
2021-01-14 11:24:56.373 1136-1136/? I/Project: FormScreen - initEditTextListener - afterTextChanged

UPDATE: This is some Kotlin behaviour. Problem was with formFirstEt?.setText("") I expected it will add empty String to EditText, but apperently it is adding character terminator. Kotlin cant recognize that its String but its char array by default with \0 character at the end. This char array is triggering some weird behaviour inside native setText function which is spamming afterTextChanged

Fix:

//type declaration String is important without it it will not work
val emptyString: String = ""
formFirstEt?.setText(emptyString)
martin1337
  • 2,384
  • 6
  • 38
  • 85

1 Answers1

0

Same problem here. I have a DialogFragment where I use a TextInputEditText with a watcher. If I addTextChangedListener in onCreateView (universally expected when using a DialogFragment), then the listener fires either once or multiple times per keystroke (at random).

Even using a local variable watcherEnabled to ensure that formatting the entered value doesn't retrigger the watcher fails.

Tried moving the assignment of the watcher to onResume instead. This feels utterly ridiculous and wrong, but seems to have solved the issue.

Also note that the text watcher appears to be firing multiple times only when using the computer keyboard to enter text into the emulator. When I use the emulator's keyboard, the watcher does not appear to misfire. This COULD indicate the bug is solely with the emulator.

rmirabelle
  • 6,268
  • 7
  • 45
  • 42