我的应用有一个SearchView。当用户键入SearchView时,onQueryTextChange将查询传递给演示者,然后调用API。我正在使用Retrofit和RxJava进行调用。调用返回一个json文件,其中包含用户目前键入的内容。问题在于,如果用户快速输入字母并且网络速度慢,则有时SearchView不会根据所有键入的字母显示结果,但可能最后一次显示结果,因为最后一次调用更快结果与第二个结果相比较。
实施例: 用户开始输入:
"凑" - >拨打API(3个字母后的第一个电话) - >开始返回值
" N" - >拨打电话 - >开始返回值
" T" - >拨打电话 - >开始返回值
" R" - >打电话(连接很慢)
" Y" - >拨打电话 - >开始返回值
- > " R"最后获得结果并返回它们
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
}
通话非常简单,每当我收到错误时,我都不想显示任何内容。
有办法解决这个问题吗?或者也许这不是使用反应式编程的情况?
编辑: 为了更清楚,流程如下:
使用自定义搜索视图的活动(https://github.com/Mauker1/MaterialSearchView)
当用户开始输入时,自定义searchview有一个监听器。用户开始输入活动后,请调用Presenter。
演示者将订阅由交互者返回的观察者:
主持人:
addSubscription(mInteractor.getValues(query)
.observeOn(mMainScheduler)
.subscribeOn(mIoScheduler)
.subscribe(data -> {
getMvpView().showValues(data);
}, e -> {
Log.e(TAG, e.getMessage());
}));
相互作用物:
public Observable<List<MyModel>> getValues(String query) {
return Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(2, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
所以,现在要么改变正常的自定义搜索视图&#39; searchview,然后使用RxBinding或者我应该使用处理程序或类似的东西(但仍然在努力如何使其适应我的架构)
答案 0 :(得分:1)
你很幸运,有一个叫做去抖动的算子
Observable.defer(() -> mNetworkService.getAPI()
.getValues(query)
.debounce(3, TimeUnit.SECONDS)
.retry(2)
.onErrorReturn(e -> new ArrayList<>()));
debounce的作用是在继续之前等待N个时间单位以获得更多结果。例如,网络需要2秒才能返回,并且您在请求后请求泛洪,debounce将等待3秒没有结果,然后返回最后的结果。可以把它想象成除了N时间之前的所有东西。
这解决了你的问题,但仍然会泛滥网络,理想情况下你会使用优秀的RxBinding库,在发出请求之前做延迟:
RxTextView.textChanges(searchView)
.debounce(3, TimeUnit.SECONDS)
.map(input->mNetworkService.getAPI().getValues(input.queryText().toString()))
.retry(2)
.onErrorReturn(e -> new ArrayList<>()))
使用当前设置,它将在用户输入内容后等待3秒,然后才进行网络呼叫。如果他们开始输入新内容,则会删除第一个待处理的搜索请求。
编辑:更改为基于OP不使用Android SearchView小部件的RxTextView.textChanges(textview)
答案 1 :(得分:1)
首先将您的Searchview设为可观察,以便您可以应用Rx运算符。 要将searchview转换为Observable
public static Observable<String> fromview(SearchView searchView) {
final PublishSubject<String> subject = PublishSubject.create();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
subject.onComplete();
searchView.clearFocus(); //if you want to close keyboard
return false;
}
@Override
public boolean onQueryTextChange(String text) {
subject.onNext(text);
return false;
}
});
return subject;
}
private void observeSearchView() {
disposable = RxSearchObservable.fromview(binding.svTweet)
.debounce(300, TimeUnit.MILLISECONDS)
.filter(text -> !text.isEmpty() && text.length() >= 3)
.map(text -> text.toLowerCase().trim())
.distinctUntilChanged()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
您可以应用过滤条件 RxJava 反跳()运算符可延迟执行任何操作,直到用户短暂暂停为止。
使用 distinctUntilChanged ()可确保用户可以搜索相同的内容两次,但不能立即背对背
在这种情况下, filter 运算符用于过滤不需要的字符串(例如空字符串),以避免不必要的网络调用。
答案 2 :(得分:0)
扩展@MikeN所说的内容,如果你只想使用LAST输入的结果,你应该使用switchMap()
(在其他一些Rx实现中为flatMapLatest()
)。
答案 3 :(得分:0)
我在不使用RxBinding的情况下解决了洪水问题,我想发布我的解决方案以防万一其他人需要它。 因此,每当调用onTextChanged时,我首先检查大小是否为&gt; 2,如果它连接到网络(由BroadcastReceiver更新布尔值)。然后我创建要发送的消息已延迟,我删除队列中的所有其他消息。这意味着我将只执行不在指定延迟范围内的查询:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.getTrimmedLength(s) > 2 && isConnected) {
mHandler.removeMessages(QUERY_MESSAGE);
Message message = Message.obtain(mHandler, QUERY_MESSAGE, s.toString().trim());
mHandler.sendMessageDelayed(message, MESSAGE_DELAY_MILLIS);
}
}
然后是处理程序:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == QUERY_MESSAGE) {
String query = (String)msg.obj;
mPresenter.getValues(query);
}
}
};
答案 4 :(得分:0)
"com.jakewharton.rxbinding2:rxbinding-kotlin:2.1.1"
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.toolbar_menu, menu)
// Associate searchable configuration with the SearchView
val searchManager = requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.setSearchableInfo(
searchManager.getSearchableInfo(requireActivity().componentName)
)
searchView.maxWidth = Integer.MAX_VALUE
// listening to search query text change
disposable = RxSearchView.queryTextChangeEvents(searchView)
.debounce(750, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
callApi(it.queryText().toString())
}, {
Timber.e(it)
})
super.onCreateOptionsMenu(menu, inflater)
}
private fun callApi(query: String){
if(!apiDisposable.isDisposed){
apiDisposable.dispose()
}
apiDisposable = mNetworkService.getAPI(query)
}