Sardor Narziyev
Sardor's Blog

Sardor's Blog

TextChangedListener with debounce

TextChangedListener with debounce

With pure Kotlin no Coroutines!

Sardor Narziyev's photo
Sardor Narziyev

Published on Nov 7, 2021

3 min read

In many cases where we override Text Watcher or similar method we end up not using all methods offered by an particular API. This leads to unused overidden method decreasing code cleanness. Below we will see how we can avoid this with Kotlin language features and implement simple debounced textChangedListener overriding only it’s one method. First let’s take a look default TextWatcher implementation

private val simpleTextWatcher = simpleEditText.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

    }

    override fun afterTextChanged(s: Editable?) {

    }
})

and for Java it will look something like this

EditText editText = findViewById(R.id.ed);

editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        //do something
    }
});

As I told before there is a problem with this approach. If you have multiple EditText like widgets in your UI you end up overriding multiple similar method. However I have got a good news for you. There is a simple sollution to this problem. Functions are first class citizen in Kotlin and it supports other many functional programming concepts. In this example we will use high-order functions.

fun EditText.textChangedDebounce(action: (text: String) -> Unit) {

    this.addTextChangedListener(object : TextWatcher {

      override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

      override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}

      override fun afterTextChanged(s: Editable?) {
            action(s.toString())
        }

    })
}

But Java does not have such a language features so you can implement your own TextWatcher like this.

public abstract class SimpleTextWatcher implements TextWatcher {

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        afterTextChanged(s.toString());
    }

    public abstract void afterTextChanged(String s);
}

You may ask what about debounce you promised us? It is as simple as this

fun TextInputEditText.textChangedDebounce(debounceTime: Long = 100L, action: (text: String) -> Unit) {
    this.addTextChangedListener(object : TextWatcher {

        private var lastTextChangedTime: Long = 0

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
        override fun afterTextChanged(s: Editable?) {
            if (SystemClock.elapsedRealtime() - lastTextChangedTime < debounceTime) return
            else action(s.toString())
            lastTextChangedTime = SystemClock.elapsedRealtime()
        }

    })
}

But you are not limited with only TextWatcher you can take a heavy use of this technique in many other ways. For example your button does an network call which is pretty heavy operation in mobile applications. So you want click to be debounced. You can implement clickWithDebounce like this

fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) {
    this.setOnClickListener(object : View.OnClickListener {
        private var lastClickTime: Long = 0

        override fun onClick(v: View) {
            if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
            else action()

            lastClickTime = SystemClock.elapsedRealtime()
        }
    })
}

Voilà! Enjoy Kotlin! Don’t forget to subscribe!

 
Share this