How to implement search feature with SearchView, R

2019-03-11 12:51发布

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.

2条回答
我只想做你的唯一
2楼-- · 2019-03-11 13:08

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

查看更多
你好瞎i
3楼-- · 2019-03-11 13:15

I use such an approach on a production app.

 RxSearchView.queryTextChanges(searchView)
                    .debounce(300, TimeUnit.MILLISECONDS)
                    .filter(new Predicate<String>() {
                        @Override
                        public boolean test(String text) throws Exception {
                            if (text.isEmpty()) {
                                return false;
                            } else {
                                return true;
                            }
                        }
                    })
                    .distinctUntilChanged()
                    .switchMap(new Function<String, ObservableSource<String>>() {
                        @Override
                        public ObservableSource<String> apply(String query) throws Exception {
                            return dataFromNetwork(query);
                        }
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<String>() {
                        @Override
                        public void accept(String result) throws Exception {
                            textViewResult.setText(result);
                        }
                    });
  • DistinctUntilChanged: The distinctUntilChanged operator is used to avoid the duplicate network calls. Let say the last on-going search query was “abc” and the user deleted “c” and again typed “c”. So again it’s “abc”. So if the network call is already going on with the search query “abc”, it will not make the duplicate call again with the search query “abc”. So, distinctUntilChanged suppress duplicate consecutive items emitted by the source Observable.
  • SwitchMap: Here, the switchMap operator is used to avoid the network call results which are not needed more for displaying to the user. Let say the last search query was “ab” and there is an ongoing network call for “ab” and the user typed “abc”. Then you are no more interested in the result of “ab”. You are only interested in the result of “abc”. So, the switchMap comes to the rescue. It only provides the result for the last search query(most recent) and ignores the rest.
查看更多
登录 后发表回答