Dev Dev - 2 months ago 86
Android Question

How to implement search feature with SearchView, Retrofit and RxJava (RxBindings)?

When the user types into the

SearchView
widget, the app should make an
API call (in background thread) to fetch search results from server, and display them (in UI thread) in RecyclerView.

I use the following code in my fragment:


@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.my_fragment, menu);
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();

SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));

RxSearchView.queryTextChanges(searchView)
.debounce(400, TimeUnit.MILLISECONDS)
.map(CharSequence::toString)
.switchMap(query -> retrofitService.search(query))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Item>>() {
@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {
Log.e(LOG_TAG, "Error", e);
}

@Override
public void onNext(List<Item> items) {
// adapter.addItems(...)
}
});
}


But I get an exception:

java.lang.IllegalStateException: Must be called from the main thread. Was: Thread[RxIoScheduler-2,5,main]
at com.jakewharton.rxbinding.internal.Preconditions.checkUiThread(Preconditions.java:35)
at com.jakewharton.rxbinding.support.v7.widget.SearchViewQueryTextChangesOnSubscribe.call(SearchViewQueryTextChangesOnSubscribe.java:18)
at com.jakewharton.rxbinding.support.v7.widget.SearchViewQueryTextChangesOnSubscribe.call(SearchViewQueryTextChangesOnSubscribe.java:10)
...


When I remove
.subscribeOn(Schedulers.io())
, the search API call is fired when fragment is created and no query is typed in
SearchView
and I get exeption

retrofit2.adapter.rxjava.HttpException: HTTP 422


then, when I type my search query
retrofitService.search(query)
is no longer called.

Answer

Remember that you can actually use multiple observeOn and multiple subscribeOn operators in your rx chain.

Try this:

RxSearchView.queryTextChanges(searchView)
            .debounce(400, TimeUnit.MILLISECONDS)
            .map(CharSequence::toString)
            .subscribeOn(AndroidSchedulers.mainThread())
            .observeOn(Schedulers.io())
            .switchMap(query -> retrofitService.search(query))
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<Item>>() {
                @Override
                public void onCompleted() {

                }

                @Override
                public void onError(Throwable e) {
                    Log.e(LOG_TAG, "Error", e);
                }

                @Override
                public void onNext(List<Item> items) {
                    // adapter.addItems(...)
                }
            });

This will basically result in this Thread usage:

thread usage

Comments