How can I do something 0.5 seconds after text changed in my EditText control?

I am filtering my list using an EditText control. I want to filter the list 0.5 seconds after the user has finished typing in EditText. I used the afterTextChanged event of TextWatcher for this purpose. But this event rises for each character changes in EditText.

What should I do?


Use:

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

        private Timer timer = new Timer();
        private final long DELAY = 1000; // Milliseconds

        @Override
        public void afterTextChanged(final Editable s) {
            timer.cancel();
            timer = new Timer();
            timer.schedule(
                new TimerTask() {
                    @Override
                    public void run() {
                        // TODO: Do what you need here (refresh list).
                        // You will probably need to use
                        // runOnUiThread(Runnable action) for some
                        // specific actions (e.g., manipulating views).
                    }
                },
                DELAY
            );
        }
    }
);

The trick is in canceling and rescheduling Timer each time, when text in EditText gets changed.

For how long to set the delay, see this post.


Better use Handler with the postDelayed() method. In the Android's implementation, Timer will create a new thread each time to run the task. Handler, however, has its own Looper that can be attached to whatever thread we wish, so we won't pay an extra cost to create a thread.

Example

 Handler handler = new Handler(Looper.getMainLooper() /*UI thread*/);
 Runnable workRunnable;
 @Override public void afterTextChanged(Editable s) {
    handler.removeCallbacks(workRunnable);
    workRunnable = () -> doSmth(s.toString());
    handler.postDelayed(workRunnable, 500 /*delay*/);
 }

 private final void doSmth(String str) {
    //
 }

You can use RxBindings; it's the best solution. See the guide to RxJava operator debounce. I'm sure that will do great in your case.

RxTextView.textChanges(editTextVariableName)
            .debounce(500, TimeUnit.MILLISECONDS)
            .subscribe(new Action1<String>() {
                @Override
                public void call(String value) {
                    // Do some work with the updated text
                }
            });

With Kotlin extension functions and coroutines:

fun AppCompatEditText.afterTextChangedDebounce(delayMillis: Long, input: (String) -> Unit) {
    var lastInput = ""
    var debounceJob: Job? = null
    val uiScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(editable: Editable?) {
            if (editable != null) {
                val newtInput = editable.toString()
                debounceJob?.cancel()
                if (lastInput != newtInput) {
                    lastInput = newtInput
                    debounceJob = uiScope.launch {
                        delay(delayMillis)
                        if (lastInput == newtInput) {
                            input(newtInput)
                        }
                    }
                }
            }
        }

        override fun beforeTextChanged(cs: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(cs: CharSequence?, start: Int, before: Int, count: Int) {}
})}