Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Throttle onQueryTextChange in SearchView

What's the best way to "throttle" onQueryTextChange so that my performSearch() method is called only once every second instead of every time the user types?

public boolean onQueryTextChange(final String newText) {
    if (newText.length() > 3) {
        // throttle to call performSearch once every second
        performSearch(nextText);
    }
    return false;
}
like image 706
aherrick Avatar asked Jan 22 '16 20:01

aherrick


6 Answers

For Korlin

In case of coroutineScope.launch(Dispatchers.Main) {} you may run into a problem Suspend function '...' should be called only from a coroutine or another suspend function.

I found the following way

private var queryTextChangedJob: Job? = null
private lateinit var searchText: String

Next don't forget use implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha05"

override fun onQueryTextChange(newText: String?): Boolean {

    val text = newText ?: return false
    searchText = text

    queryTextChangedJob?.cancel()
    queryTextChangedJob = lifecycleScope.launch(Dispatchers.Main) {
        println("async work started...")
        delay(2000)
        doSearch()
        println("async work done!")
    }

    return false
}

If you want use launch inside ViewModel then use implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha05"

queryTextChangedJob = viewModelScope.launch(Dispatchers.Main) {
        //...
}
like image 122
MaxB Avatar answered Nov 10 '22 23:11

MaxB


If you are using Kotlin and coroutines you could do the following:

var queryTextChangedJob: Job? = null

...

fun onQueryTextChange(query: String) {

    queryTextChangedJob?.cancel()
    
    queryTextChangedJob = launch(Dispatchers.Main) {
        delay(500)
        performSearch(query)
    }
}
like image 40
jc12 Avatar answered Nov 10 '22 22:11

jc12


  1. Create abstract class:

    public abstract class DelayedOnQueryTextListener implements SearchView.OnQueryTextListener {
    
     private Handler handler = new Handler();
     private Runnable runnable;
    
     @Override
     public boolean onQueryTextSubmit(String s) {
         return false;
     }
    
     @Override
     public boolean onQueryTextChange(String s) {
         handler.removeCallbacks(runnable);
         runnable = () -> onDelayerQueryTextChange(s);
         handler.postDelayed(runnable, 400);
         return true;
     }
    
     public abstract void onDelayedQueryTextChange(String query);
    

    }

  2. Set it like this:

    searchView.setOnQueryTextListener(new DelayedOnQueryTextListener() {
         @Override
         public void onDelayedQueryTextChange(String query) {
             // Handle query
         }
     });
    
like image 36
Johnny Five Avatar answered Nov 11 '22 00:11

Johnny Five


Building on aherrick's code, I have a better solution. Instead of using a boolean 'canRun', declare a runnable variable and clear the callback queue on the handler each time the query text is changed. This is the code I ended up using:

@Override
public boolean onQueryTextChange(final String newText) {
    searchText = newText;

    // Remove all previous callbacks.
    handler.removeCallbacks(runnable);

    runnable = new Runnable() {
        @Override
        public void run() {
            // Your code here.
        }
    };
    handler.postDelayed(runnable, 500);

    return false;
}
like image 39
Amit Avatar answered Nov 10 '22 22:11

Amit


I've come up to a solution using RxJava, particularly it's debounce operator.

With Jake Wharton's handy RxBinding, we'll have this:

RxSearchView.queryTextChanges(searchView)
        .debounce(1, TimeUnit.SECONDS) // stream will go down after 1 second inactivity of user
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<CharSequence>() {
            @Override
            public void accept(@NonNull CharSequence charSequence) throws Exception {
                // perform necessary operation with `charSequence`
            }
        });
like image 9
azizbekian Avatar answered Nov 10 '22 22:11

azizbekian


You can easily achieve it with RxJava. Also, you will need RxAndroid and RxBinding (but you probably already have them in your project if you are using RxJava).

RxTextView.textChangeEvents(yourEditText)
          .debounce(1, TimeUnit.SECONDS)
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(performSearch());

Here the full example by Kaushik Gopal.

like image 2
Geralt_Encore Avatar answered Nov 10 '22 23:11

Geralt_Encore