私の現在のAndroidアプリケーションでは、ユーザーはコンテンツをリモートで検索できます。
たとえば、ユーザーにはEditText
、検索文字列を受け入れ、入力されたテキストに一致する結果を返すリモートAPI呼び出しをトリガーするが表示されます。
最悪の場合は、を追加してTextWatcher
、呼び出されるたびにAPI呼び出しをトリガーするだけonTextChanged
です。これは、最初のAPI呼び出しを行う前に、ユーザーに検索するために少なくともN文字を入力するように強制することで改善できます。
「完璧な」ソリューションには、次の機能があります。-
ユーザーが検索文字列の入力を開始すると
定期的に(Mミリ秒ごとに)入力された文字列全体を消費します。期間が終了し、現在のユーザー入力が前のユーザー入力と異なるたびにAPI呼び出しをトリガーします。
[入力したテキストの長さに関連する動的タイムアウトを設定することはできますか?たとえば、テキストが「短い」間、API応答サイズは大きくなり、戻りと解析に時間がかかります。検索テキストが長くなると、API応答サイズは「機内」および解析時間とともに減少します]
ユーザーがEditTextフィールドへの入力を再開すると、テキストの定期的な消費が再開されます。
ユーザーがENTERキーを押すたびに、「最終」API呼び出しがトリガーされ、EditTextフィールドへのユーザー入力の監視が停止されます。
API呼び出しがトリガーされる前にユーザーが入力する必要のあるテキストの最小長を設定しますが、ユーザーが「短い」テキスト文字列を検索したいときにできるように、この最小長をオーバーライドするタイムアウト値と組み合わせます。
RxJavaやRxBindingsは上記の要件をサポートできると確信していますが、これまでのところ、実行可能なソリューションを実現できていません。
私の試みは含まれています
private PublishSubject<String> publishSubject;
publishSubject = PublishSubject.create();
publishSubject.filter(text -> text.length() > 2)
.debounce(300, TimeUnit.MILLISECONDS)
.toFlowable(BackpressureStrategy.LATEST)
.subscribe(new Consumer<String>() {
@Override
public void accept(final String s) throws Exception {
Log.d(TAG, "accept() called with: s = [" + s + "]");
}
});
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
publishSubject.onNext(s.toString());
}
@Override
public void afterTextChanged(final Editable s) {
}
});
そしてこれはRxBindingで
RxTextView.textChanges(mEditText)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(new Consumer<CharSequence>(){
@Override
public void accept(final CharSequence charSequence) throws Exception {
Log.d(TAG, "accept() called with: charSequence = [" + charSequence + "]");
}
});
どちらも、入力されたテキストの長さとタイムアウト値を組み合わせた条件付きフィルターを提供しません。
また、debounceをthrottleLastに置き換え、どちらも必要なソリューションを提供しなかったサンプルを作成しました。
必要な機能を実現することはできますか?
動的タイムアウト
許容できるソリューションは、次の3つのシナリオに対応します。
私)。ユーザーが「P」で始まる任意の単語を検索したい
ii)。ユーザーが「Pneumo」で始まる単語を検索したい
iii)。ユーザーが「ニューモノウルトラマイクロスコピックシリコボルカノコニオーシス」という単語を検索したい
3つのシナリオすべてで、ユーザーが文字「P」を入力するとすぐに、進行状況スピナーが表示されます(ただし、この時点ではAPI呼び出しは実行されません)。レスポンシブUI内でユーザーに検索フィードバックを提供する必要性と、ネットワークを介して「無駄な」API呼び出しを行う必要性のバランスを取りたいと思います。
ユーザーが検索テキストを入力してから「完了」(または「Enter」)キーをクリックすることに頼ることができれば、最後のAPI呼び出しをすぐに開始できます。
シナリオ1
ユーザーが入力したテキストの長さが短いため(たとえば、1文字の長さ)、タイムアウト値は最大値になります。これにより、ユーザーは追加の文字を入力でき、「無駄なAPI呼び出し」を節約できます。
ユーザーが文字「P」だけを検索したいので、最大タイムアウトが経過したら、API呼び出しを実行して結果を表示します。このシナリオでは、動的タイムアウトが期限切れになるのを待ってから、ラージAPI応答が返され、表示されるのを待つ必要があるため、ユーザーエクスペリエンスが最悪になります。中間検索結果は表示されません。
シナリオ2
このシナリオは、ユーザーが6文字すべてを「すばやく」入力した場合にユーザーが何を検索するのか(または検索文字列の最終的な長さ)がわからないため、シナリオ1を組み合わせたものです。1つのAPI呼び出しを実行できますが、6文字の入力が遅くなります。文字を使用すると、無駄なAPI呼び出しを実行する可能性が高くなります。
このシナリオでは、動的タイムアウトが期限切れになるのを待つ必要があるため、ユーザーエクスペリエンスが向上しますが、中間検索結果が表示される可能性があります。API応答は、シナリオ1よりも小さくなります。
シナリオ3
このシナリオは、シナリオ1と2を組み合わせたものです。ユーザーが45文字すべてを「すばやく」入力した場合、ユーザーが何を検索するのか(または検索文字列の最終的な長さ)がわからないため、1つのAPI呼び出しを実行できます(多分!)。 45文字の入力が遅いと、無駄なAPI呼び出しを実行する可能性が高くなります。
私は、希望するソリューションを提供するテクノロジーに縛られていません。Rxは私がこれまでに特定した最良のアプローチだと思います。
このようなものはうまくいくはずです(実際には試していませんでした)
Single<String> firstTypeOnlyStream = RxTextView.textChanges(mEditText)
.skipInitialValue()
.map(CharSequence::toString)
.firstOrError();
Observable<CharSequence> restartTypingStream = RxTextView.textChanges(mEditText)
.filter(charSequence -> charSequence.length() == 0);
Single<String> latestTextStream = RxTextView.textChanges(mEditText)
.map(CharSequence::toString)
.firstOrError();
Observable<TextViewEditorActionEvent> enterStream =
RxTextView.editorActionEvents(mEditText, actionEvent -> actionEvent.actionId() == EditorInfo.IME_ACTION_DONE);
firstTypeOnlyStream
.flatMapObservable(__ ->
latestTextStream
.toObservable()
.doOnNext(text -> nextDelay = delayByLength(text.length()))
.repeatWhen(objectObservable -> objectObservable
.flatMap(o -> Observable.timer(nextDelay, TimeUnit.MILLISECONDS)))
.distinctUntilChanged()
.flatMap(text -> {
if (text.length() > MINIMUM_TEXT_LENGTH) {
return apiRequest(text);
} else {
return Observable.empty();
}
})
)
.takeUntil(restartTypingStream)
.repeat()
.takeUntil(enterStream)
.mergeWith(enterStream.flatMap(__ ->
latestTextStream.flatMapObservable(this::apiRequest)
))
.subscribe(requestResult -> {
//do your thing with each request result
});
アイデアは、X回ごとにサンプリングするという要件に基づいて、テキスト変更イベント自体ではなく、サンプリングに基づいてストリームを構築することです。
ここで私が行った方法は、1つのストリームを作成することです(firstTypeOnlyStream
イベントの最初のトリガー(ユーザーがテキストを初めて入力するとき)の場合、このストリームは、ユーザーの最初の入力で処理ストリーム全体を開始します。トリガーが到着したら、基本的に定期的に編集テキストをサンプリングしますlatestTextStream
。これlatestTextStream
は実際には時間の経過に伴うストリームではなく、RxBindingEditText
のInitialValueObservable
プロパティを使用した現在の状態のサンプリングです(サブスクリプションで現在のテキストを出力するだけですEditText
)。言い換えれば、これはサブスクリプションで現在のテキストを取得するための素晴らしい方法であり、
Observable.fromCallable(() -> mEditText.getText().toString());
次と同等です。次に、動的タイムアウト/遅延の場合nextDelay
、テキストの長さに基づいて更新しrepeatWhen
、タイマーを使用して目的の時間を待機します。distinctUntilChanged
、テキストの長さに基づいて必要なサンプリングを提供する必要があります。さらに、テキストに基づいてリクエストを送信します(十分な長さの場合)。
入力による停止-使用はtakeUntil
してenterStream
いる入力にトリガーされると、それはまた、最終的なクエリをトリガします。
再起動-ユーザーが '再起動'入力を開始すると-つまり、テキストが空の場合、.takeUntil(restartTypingStream)
+repeat()
は空の文字列が入力されたときにストリームを停止し、再起動(再サブスクライブ)します。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加