'How to avoid multiple triggers on EditText while user is typing?

I use the following code to perform search when user types in an EditText :

EditText queryView = (EditText) findViewById(R.id.querybox);
queryView.addTextChangedListener(new TextWatcher() {
  @Override
  public void afterTextChanged(Editable s) {
    triggerSearch(s.toString()); 
  }
  @Override
  public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  }
  @Override
  public void onTextChanged(CharSequence s, int start, int before, int count) {
  }
});

However, this triggers multiple times when the user is typing a word. That is if the user is typing "hello", this code will trigger 5 times with values ("h", "he" , "hel", "hell", "hello"). Normally, this would be fine but the triggered search is expensive and I don't want to waste resources on intermediate searches that are of no great use. What I want is either a listener that triggers only a certain threshold after the user starts typing, or some kind of framework, that waits in the listener before calling triggerSearch, and if another event is triggered before that wait, cancels itself.



Solution 1:[1]

Since couldn't find an appropriate event interface, tried triggering a delayed search. The code is actually pretty simple and robust.

private final int TRIGGER_SERACH = 1;
// Where did 1000 come from? It's arbitrary, since I can't find average android typing speed.
private final long SEARCH_TRIGGER_DELAY_IN_MS = 1000;

  private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      if (msg.what == TRIGGER_SERACH) {
        triggerSearch();
      }
    }
  };

 queryView.addTextChangedListener(new TextWatcher() {

   @Override
   public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {

   }

   @Override
   public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {

   }

   @Override
   public void afterTextChanged(Editable s) {
    handler.removeMessages(TRIGGER_SERACH);
    handler.sendEmptyMessageDelayed(TRIGGER_SERACH, SEARCH_TRIGGER_DELAY_IN_MS);
   });

Solution 2:[2]

One solution would be to not execute your triggered search immediately but after some interval, say 100ms, has passed since the onTextChanged method gets called. Additionally reset this interval each time text is typed.

This way the triggered search doesn't get called while the user is typing.

Solution 3:[3]

To wait in the listener before calling triggerSearch; test the length of Editable s.

if(s.length() > THRESHOLD)

Solution 4:[4]

If you don't want to trigger the search on every button pressed, then you need to know when the user is done. I would avoid complex ideas of attempting to figure out when the user is done typing. It will lead to the user being confused as to when the search occurs.

Simple answer then is put a "done" button and apply an onClickListener on the button and execute your search there.

Easiest code: in xml

<Button
android:onClick="onDoneClicked"
... the rest of your button layout
/>

in java:

@Override
public void onDoneClicked(View view){
EditText queryView = (EditText) findViewById(R.id.querybox);
triggerSearch(queryView.toString());
}

Solution 5:[5]

I know this is a rather old question and it's also related to this question but I think @apoorv020 answer is still relevant for both this and that question. So here is a Kotlin version of his answer.

SimpleDelayedTextWatcher.kt

class SimpleDelayedTextWatcher(
    private val delay: Long,
    doAfterTextChanged: () -> Unit
) : TextWatcher {

    private val magic = 1

    private val handler = Handler(Looper.getMainLooper()) {
        if (it.what == magic) doAfterTextChanged()
        true
    }

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

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

    override fun afterTextChanged(s: Editable?) {
        handler.removeMessages(magic)
        handler.sendEmptyMessageDelayed(magic, delay)
    }
}

usage:

var editText = binding....
editText = findViewById...

editText.addTextChangedListener(SimpleDelayedTextWatcher(1000L) { 
/* do something here */
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ABros
Solution 2 slayton
Solution 3 Sam
Solution 4 Kyle
Solution 5 YaMiN