How can I do something 0.5 seconds after text changed in my EditText control?
AndroidFilterAndroid EdittextTextwatcherTextchangedAndroid 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;
}
});