私のアプリの検索機能には、次のことを行うホット オブザーバブル チェーンがあります。
- ユーザー入力文字列を
EditText
(aTextChangedEvent
) に受け入れます (onmainThread
) - デバウンス 300ms (
computation
スレッド上) - ローディングスピナーを表示 (
mainThread
) - その文字列を使用して SQL データベースにクエリを実行します (このクエリには 100 ミリ秒から 2000 ミリ秒かかる場合があります) (on
Schedulers.io()
) - 結果をユーザーに表示する (
mainThread
)
ステップ 3 の長さは非常に可変であるため、最近の検索結果よりも新しい検索結果が表示される競合状態が発生します (場合によっては)。ユーザーが を入力したいとしますがchicken
、入力速度が異常なため、単語の最初の部分が単語全体の前に出力されます。
- の検索
chick
が最初に送信され、続いて が送信されchicken
ます。 - 実行に
chick
かかると言いますが、実行にかかります。1500ms
chicken
300ms
- これにより、
chick
検索語に対する検索結果が正しく表示されなくなりますchicken
。これは、chicken
検索が最初に完了し (わずか 300 ミリ秒しかかからなかった)、続いて検索が完了したためchick
です (1500 ミリ秒)。
このシナリオをどのように処理できますか?
- ユーザーが を介して新しい検索をトリガーする
TextChangedEvent
と、古い検索がまだ実行されていても、私は気にしません。古い検索をキャンセルする方法はありますか?
完全な観察可能なコード:
subscription = WidgetObservable.text(searchText)
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
//do this on main thread because it's a UI element (cannot access a View from a background thread)
//get a String representing the new text entered in the EditText
.map(new Func1<OnTextChangeEvent, String>() {
@Override
public String call(OnTextChangeEvent onTextChangeEvent) {
return onTextChangeEvent.text().toString().trim();
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(new Action1<String>() {
@Override
public void call(String s) {
presenter.handleInput(s);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return s != null && s.length() >= 1 && !s.equals("");
}
}).doOnNext(new Action1<String>() {
@Override
public void call(String s) {
Timber.d("searching for string: '%s'", s);
}
})
//run SQL query and get a cursor for all the possible search results with the entered search term
.flatMap(new Func1<String, Observable<SearchBookmarkableAdapterViewModel>>() {
@Override
public Observable<SearchBookmarkableAdapterViewModel> call(String s) {
return presenter.getAdapterViewModelRx(s);
}
})
.subscribeOn(Schedulers.io())
//have the subscriber (the adapter) run on the main thread
.observeOn(AndroidSchedulers.mainThread())
//subscribe the adapter, which receives a stream containing a list of my search result objects and populates the view with them
.subscribe(new Subscriber<SearchBookmarkableAdapterViewModel>() {
@Override
public void onCompleted() {
Timber.v("Completed loading results");
}
@Override
public void onError(Throwable e) {
Timber.e(e, "Error loading results");
presenter.onNoResults();
//resubscribe so the observable keeps working.
subscribeSearchText();
}
@Override
public void onNext(SearchBookmarkableAdapterViewModel searchBookmarkableAdapterViewModel) {
Timber.v("Loading data with size: %d into adapter", searchBookmarkableAdapterViewModel.getSize());
adapter.loadDataIntoAdapter(searchBookmarkableAdapterViewModel);
final int resultCount = searchBookmarkableAdapterViewModel.getSize();
if (resultCount == 0)
presenter.onNoResults();
else
presenter.onResults();
}
});