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

AndroidFilterAndroid EdittextTextwatcherTextchanged

Android Problem Overview


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?

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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) {
    //
 }

Solution 3 - Android

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
                }
            });

Solution 4 - Android

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) {}
})}

Solution 5 - Android

None of the above solutions worked for me.

I needed a way for TextWatcher to not fire on every character I input inside my search view and show some progress, meaning I need to access the UI thread.

private final TextWatcher textWatcherSearchListener = new TextWatcher() {
    final android.os.Handler handler = new android.os.Handler();
    Runnable runnable;

    public void onTextChanged(final CharSequence s, int start, final int before, int count) {
        handler.removeCallbacks(runnable);
    }

    @Override
    public void afterTextChanged(final Editable s) {
        // Show some progress, because you can access UI here
        runnable = new Runnable() {
            @Override
            public void run() {
                // Do some work with s.toString()
            }
        };
        handler.postDelayed(runnable, 500);
    }

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

Removing Handler on every onTextChanged (which is called when the user inputs a new character). afterTextChanged is called after the text has been changed inside the input field where we can start a new Runnable, but it will cancel it if the user types more characters (for more information, when these callback are called, see this). If the user doesn't input any more characters, the interval will pass in postDelayed and it will call work you should do with that text.

This code will run only once per interval, not for every key user inputs.

Solution 6 - Android

How do you determine that they have finished writing? That the edittext loses focus? Then there is setOnFocusChangedListener.

Responding to latest edit in question: If you want to wait a specific time after the latest key stroke, then you have to start up a thread at the first keypress (use TextWatcher). Constantly register the time of the latest key stroke. Let the thread sleep to the the time of the latest keystroke + 0.5 seconds. If the timestamp of the latest keystroke has not been updated, do whatever you had intended.

Solution 7 - Android

In the Kotlin language, you can do it like this:

tv_search.addTextChangedListener(mTextWatcher)

private val mTextWatcher: TextWatcher = object : TextWatcher {
    private var timer = Timer()
    private val DELAY: Long = 1000L

    override fun afterTextChanged(s: Editable?) {
        timer.cancel()
        timer = Timer()
        timer.schedule(object : TimerTask() {
            override fun run() {

                // Do your stuff here
            }
        }, DELAY)
    }

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

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

}

Solution 8 - Android

You can also use the TextWatcher interface and create your custom class that implements it to reuse your CustomTextWatcher many times and also you can pass views or whatever you might need to its constructor:

public abstract class CustomTextWatcher implements TextWatcher { // Notice abstract class so we leave abstract method textWasChanged() for implementing class to define it

    private final TextView myTextView; // Remember EditText is a TextView, so this works for EditText also


    public AddressTextWatcher(TextView tView) { // Notice I'm passing a view at the constructor, but you can pass other variables or whatever you need
        myTextView = tView;
    }

    private Timer timer = new Timer();
    private final int DELAY = 500; // Milliseconds of delay for timer

    @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(final Editable s) {
        timer.cancel();
        timer = new Timer();

        timer.schedule(

            new TimerTask() {
                @Override
                public void run() {
                    textWasChanged();
                }
            },
            DELAY

        );
    }

    public abstract void textWasChanged(); // Notice the abstract method to leave the
                                           // implementation to the implementing class

}

Now in your activity you can use it like this:

// Notice I'm passing in the constructor of CustomTextWatcher
// myEditText I needed to use
myEditText.addTextChangedListener(new CustomTextWatcher(myEditText) {
    @Override
    public void textWasChanged() {
        //doSomething(); This is method inside your activity
    }
});

Solution 9 - Android

You can use a timer. After typing the text it will wait for 600 ms. Put the code inside afterTextChanged() by using a delay of 600 ms.

@Override
    public void afterTextChanged(Editable arg0) {
        // The user typed: start the timer
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // Do your actual work here
               editText.setText(et.getText().toString());
            }
        }, 600); // 600 ms delay before the timer executes the „run“ method from TimerTask
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // Nothing to do here
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // The user is typing: reset already started timer (if existing)
        if (timer != null) {
            timer.cancel();
        }
    }
};

Solution 10 - Android

That is the event while and after finish of typing ... add a textWatcher and in the onTextChanged method put:

if (charSequence.length() > 0){ 
    // Your code 
}

Solution 11 - Android

If you want to skip textWatcher for the first time only, then add the following code:

This will allow textWatcher make any change from the second time.

Boolean firstchange = false;

profileEmailEditText.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) {
        if (firstchange) {
            emailAlertText.setVisibility(View.VISIBLE);
        }
        else {
            firstchange = true;
        }
    }

    @Override
    public void afterTextChanged(Editable s) {

    }
});

Solution 12 - Android

Try this

class DelayTextWatcher(val ms: Long = 500, val textChanged: (String) -> Unit) : TextWatcher {

private var timer: CountDownTimer? = null
override fun afterTextChanged(p0: Editable) {
    timer?.cancel()
    timer = object : CountDownTimer(ms, ms) {
        override fun onTick(millisUntilFinished: Long) {

        }

        override fun onFinish() {
            textChanged(p0.toString())
        }
    }.start()
}

override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

fun dispose() {
    timer?.cancel()
}

}

Solution 13 - Android

If you write in Kotlin, you can do it like this.

This approach uses coroutines instead Thread (if you will do it via Timer()). Moreover, you can control the lifecycle of debounceJob with launchWhenCreated, etc.

private val onNumberListener = object : TextWatcher {
    private var debounceJob: Job? = null
    private val DELAY: Long = 1000L

    override fun afterTextChanged(s: Editable?) {
        debounceJob?.cancel()
        debounceJob = this@FragmentName.viewLifecycleOwner.lifecycle.coroutineScope
            .launch(Dispatchers.Main) {
                delay(DELAY)
                viewModel.onNumberChange(s?.toString() ?: "")
            }
    }

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

    }

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

    }
}

Solution 14 - Android

Another way to observe text change event is using Coroutines Channels.

lifecycleScope.launchWhenCreated {
            editText.afterTextChanged {
                // do something
            }
        }

Create an extension function to collect data from a flow

suspend fun EditText.afterTextChanged(afterTextChanged: suspend (String) -> Unit) {
    val watcher = Watcher()
    this.addTextChangedListener(watcher)

    watcher.asFlow()
        .debounce(500)
        .collect { afterTextChanged(it) }
}

Create a Watcher class to offer text after change

class Watcher : TextWatcher {

    private val channel = ConflatedBroadcastChannel<String>()

    override fun afterTextChanged(editable: Editable?) {
        channel.offer(editable.toString())
    }

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

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

    fun asFlow(): Flow<String> {
        return channel.asFlow()
    }
}

Solution 15 - Android

the best way is to move the cursor

> setSelection(it.toString().length)

with this form don't use tree or coroutine to sleep N tine

Solution 16 - Android

Using a timer for your case is not the best solution because of creating a new object every time. According to the Timer documentation, it's better to use ScheduledThreadPoolExecutor -

> "Timers schedule one-shot or recurring tasks for execution. Prefer > ScheduledThreadPoolExecutor for new code."

Here is a better approach

Runnable runnabledelayedTask = new Runnable(){
    @Override
    public void run(){
        //TODO Perform any operation here
    }
};

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 final long DELAY = 500; // Milliseconds

        @Override
        public void afterTextChanged(final Editable s) {
            ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1);
            ScheduledFuture sf = scheduledPool.schedule(callabledelayedTask, DELAY, TimeUnit.MILLISECONDS);
            // You can cancel ScheduledFuture when needed
        }
    }
);

Solution 17 - Android

You can use EditorActionListener for that purpose.

editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (actionId == EditorInfo.IME_ACTION_DONE) {
            //Do something here
            return true;
        }
        return false;
    }
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBobsView Question on Stackoverflow
Solution 1 - AndroidBerťákView Answer on Stackoverflow
Solution 2 - AndroidNazarKView Answer on Stackoverflow
Solution 3 - AndroidAnton MakhrovView Answer on Stackoverflow
Solution 4 - AndroidKyriakos GeorgiopoulosView Answer on Stackoverflow
Solution 5 - AndroidBeemoView Answer on Stackoverflow
Solution 6 - AndroidpgsandstromView Answer on Stackoverflow
Solution 7 - AndroidAndroid DevView Answer on Stackoverflow
Solution 8 - AndroidCommonSenseCodeView Answer on Stackoverflow
Solution 9 - AndroidGyan Swaroop AwasthiView Answer on Stackoverflow
Solution 10 - AndroidAbdelaziz refaatView Answer on Stackoverflow
Solution 11 - AndroidpriyamView Answer on Stackoverflow
Solution 12 - AndroidBoldijar PaulView Answer on Stackoverflow
Solution 13 - AndroidP1NG2WINView Answer on Stackoverflow
Solution 14 - AndroidVahe GharibyanView Answer on Stackoverflow
Solution 15 - AndroidDavidUpsView Answer on Stackoverflow
Solution 16 - AndroidskylerView Answer on Stackoverflow
Solution 17 - AndroidAmit JayantView Answer on Stackoverflow