如何使用SearchView、Retrofit和RxJava(RxBindings)实现搜索功能?

11
当用户在 SearchView 小部件中输入时,应用程序应该在后台线程中进行 API 调用以从服务器获取搜索结果,并在 UI 线程中在 RecyclerView 中显示它们。
我在我的 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(...)
                    }
                });
}

但我却收到了一个异常:

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)
...

当我删除.subscribeOn(Schedulers.io())时,搜索API调用会在片段创建时触发,并且在SearchView中没有输入查询时,我会得到异常。

retrofit2.adapter.rxjava.HttpException: HTTP 422 

然后,当我输入搜索查询 retrofitService.search(query) 就不再被调用了。

2个回答

21

请记住,在Rx流中,您可以实际上使用多个observeOn和多个subscribeOn运算符。

尝试以下代码:

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(...)
                }
            });

这基本上会导致以下线程使用情况:

线程使用情况


2
否则,您可以在配置Retrofit时指定Io线程 -- .addCallAdapterFactory(RxJavaCallAdapterFactory.create(Schedulers.io)) - Outofdate
是的,然后.observeOn(Schedulers.io())就不必要了。 - Bartek Lipinski

3
我在一个生产应用程序中使用这种方法。
 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: distinctUntilChanged操作符用于避免重复的网络调用。假设最后一次搜索查询是"abc",用户删除了"c"并重新输入了"c",那么又回到了"abc"。如果网络调用已经使用搜索查询"abc"进行中,它将不会再次使用搜索查询"abc"进行重复调用。因此,distinctUntilChanged可以抑制源Observable发出的连续重复项。
  • SwitchMap: 在这里,switchMap操作符用于避免显示给用户不再需要的网络调用结果。假设上一个搜索查询是"ab",正在进行一个针对"ab"的网络调用,用户输入了"abc"。那么你只对"abc"的结果感兴趣,不再关心"ab"的结果。因此,switchMap挽救了这种情况,并仅提供最近一次搜索查询的结果,并忽略其它结果。

1
你好,能分享一下你的dataFromNetwork()代码吗?我该如何在我的ViewModel中使用retrofit和Livedata来获取它呢? - Sharas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接