Search code examples
androidandroid-edittextandroid-textwatcher

Manipulate or capture EditText input efficiently


What i'm trying to achieve

I want to convert every char the user inputs to another char and display it in the EditText.

What i've done

My first approach is implemented using a TextWatcher.

     private val textWatcher2 = object : TextWatcher {
        private var byUser = true

        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 (!byUser) return

            byUser = false

            val t = [email protected]()
            val m = convert(t.last())
            val s = t.substring(0, t.length - 1) + m
            [email protected]("")
            [email protected](s)
            [email protected]([email protected]!!.length)

            byUser = true
        }
    }

    /*This is just an mock of my real implementation. But the same delay occurs*/
    fun convert(c: Char) : String {
        return c.toString()
    }

This is working: Every new input char is converted via the convert(Char) function. Unfortunately this solution is incredibly slow and inefficient. There is a short delay after each input and you can't type fast.

This solution is not suitable for a production app. But i have not found another solution yet (Debouncing or deffering via RxJava doesn't work because some chars get skipped or race conditions apply) that works better.

Any ideas, solutions or help is appreciated.


Solution

  • I changed a few things and saw a big decrease in lagginess, though I can't say which had the most (if any) impact:

    • replaced calls to setText with manipulating the Editable directly
    • moved the text manipulation from the onTextChanged callback to the afterTextChanged callback, since that's the callback that gives us an Editable and not a CharSequence
    • simplified the logic around deciding what the new text is going to be--hopefully this is still doing what you intended; it's a bit unclear to me from the sample code you provided

      private val textWatcher2 = object : TextWatcher {
           private var byUser = true
      
           override fun afterTextChanged(s: Editable?) {
               if (!byUser) return
      
               byUser = false
               s?.let {
                   if (s.isNotEmpty()) {
                       s.replace(s.length - 1, s.length, convert(s.last()))
                   }
               }
               byUser = true
           }
      
           override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
           }
      
           override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
           }
       }