RxJavaを使用したAndroidでのインスタント検索の実装

RxJavaを使用したAndroidでのインスタント検索の実装







私は通常、バックエンドサービスと通信してAPIを介してデータを受信する新しいアプリケーションに取り組んでいます。 この例では、検索機能を開発します。その機能の1つは、テキストを入力しながらすぐに検索できる機能です。







インスタント検索



複雑なことは何もありません。 検索コンポーネントをページ(ほとんどの場合ツールバー)に配置し、 onTextChange



イベントonTextChange



を接続して検索を実行するだけです。 だからここに私がやったことがあります:







 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { Log.d(TAG, "onQueryTextSubmit: $query") return false } override fun onQueryTextChange(newText: String?): Boolean { Log.d(TAG, "onQueryTextChange: $newText") return false } }) return super.onCreateOptionsMenu(menu) }
      
      





しかし、ここに問題があります。 入力中に検索を実装する必要があるため、 onQueryTextChange()



イベントハンドラーが起動するたびに、APIを使用して最初の結果セットを取得します。 ログは次のとおりです。







 D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TEST D/MainActivity: onQueryTextSubmit: TEST
      
      





リクエストを印刷しているだけですが、5つのAPI呼び出しがあり、それぞれが検索を実行します。 たとえば、クラウドでは、APIの呼び出しごとに料金を支払う必要があります。 したがって、リクエストを入力するとき、送信する前に少し遅延する必要があります。そのため、最終的にAPIへの呼び出しは1回だけです。







今、私は何か他のものを見つけたいと思います。 TESTを削除し、他の文字を入力します。







 D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: D/MainActivity: onQueryTextChange: S D/MainActivity: onQueryTextChange: SO D/MainActivity: onQueryTextChange: SOM D/MainActivity: onQueryTextChange: SOME D/MainActivity: onQueryTextChange: SOMET D/MainActivity: onQueryTextChange: SOMETH D/MainActivity: onQueryTextChange: SOMETHI D/MainActivity: onQueryTextChange: SOMETHIN D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING E D/MainActivity: onQueryTextChange: SOMETHING EL D/MainActivity: onQueryTextChange: SOMETHING ELS D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextSubmit: SOMETHING ELSE
      
      





20のAPI呼び出しがあります! 少し遅れると、これらの呼び出しの数が減ります。 また、切り取られたテキストが繰り返しリクエストにつながらないように、重複を取り除きたいです。 私もおそらくいくつかの要素を除外したいです。 たとえば、入力した文字なしで検索したり、1文字で検索したりする機能が必要ですか?







リアクティブプログラミング



さらなるアクションのためのいくつかのオプションがありますが、今は、一般的にリアクティブプログラミングとRxJavaライブラリとして知られている手法に焦点を当てたいと思います。 リアクティブプログラミングに最初に出会ったとき、私は次の説明を見ました。







ReactiveXは、非同期構造で動作し、オブザーバーパターンとイテレーターパターンの組み合わせ、および関数型プログラミング機能を使用してデータストリームまたはイベントを操作するAPIです。

この定義は、ReactiveXの性質と長所を完全には説明していません。 そして、それが説明するのであれば、このフレームワークの原則にすでに精通している人にのみ。 私もこのようなチャートを見ました:







遅延演算子チャート







この図はオペレーターの役割を説明していますが、本質を完全には理解していません。 それで、この図を簡単な例でより明確に説明できるかどうかを見てみましょう。







最初にプロジェクトを準備しましょう。 アプリケーションのbuild.gradle



ファイルに新しいライブラリが必要になります。







 implementation "io.reactivex.rxjava2:rxjava:2.1.14"
      
      





プロジェクトの依存関係を同期して、ライブラリをロードすることを忘れないでください。







それでは、新しいソリューションを見てみましょう。 古いメソッドを使用して、新しい文字を入力するたびにAPIにアクセスしました。 新しいメソッドを使用して、 Observable



を作成します。







 override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .subscribe { text -> Log.d(TAG, "subscriber: $text") } return super.onCreateOptionsMenu(menu) }
      
      





このコードは、古いコードとまったく同じことを行います。 ログは次のとおりです。







 D/MainActivity: subscriber: T D/MainActivity: subscriber: TE D/MainActivity: subscriber: TES D/MainActivity: subscriber: TEST D/MainActivity: subscriber: TEST
      
      





ただし、新しい手法を使用する場合の主な違いは、リアクティブストリームの存在Observable



です。 テキストハンドラ(この場合はリクエストハンドラ)は、 onNext()



メソッドを使用してアイテムをストリームに送信します。 Observable



は、これらの要素を処理するサブスクライバーがいます。







サブスクライブする前にメソッドのチェーンを作成して、処理する文字列のリストを減らすことができます。 まず、送信されるテキストは常に小文字であり、行の先頭と末尾にスペースはありません。







 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
      
      





最も重要な部分を示すためにメソッドをカットしました。 現在、同じログは次のとおりです。







 D/MainActivity: subscriber: t D/MainActivity: subscriber: te D/MainActivity: subscriber: tes D/MainActivity: subscriber: test D/MainActivity: subscriber: test
      
      





次に、250msの遅延を適用して、より多くのコンテンツを期待します。







 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .subscribe { text -> Log.d(TAG, "subscriber: $text" }
      
      





最後に、重複したストリームを削除して、最初の一意のリクエストのみが処理されるようにします。 後続の同一のリクエストは無視されます:







 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .subscribe { text -> Log.d(TAG, "subscriber: $text" }
      
      





ご注意 perev。 この場合、 distinctUntilChanged()



演算子を使用する方が合理的です。そうでない場合、任意の文字列で繰り返し検索を行う場合、クエリは単に無視されるためです。 そして、そのような検索を実装するとき、最後に成功したリクエストにのみ注意を払い、新しいリクエストが前のリクエストと同じ場合は無視するのが合理的です。

最後に、空のクエリを除外します。







 Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
      
      





この時点で、ログに表示されるメッセージは1つ(または2つ)だけであり、API呼び出しが少ないことを示しています。 この場合、アプリケーションは引き続き適切に動作します。 さらに、何かを入力してから削除して再度入力した場合も、APIの呼び出しが少なくなります。







目的に応じて、このパイプラインに追加できる演算子はさらに多くあります。 APIとやり取りする入力フィールドを操作するのに非常に役立つと思います。 完全なコードは次のとおりです。







 // Set up the query listener that executes the search Observable.create(ObservableOnSubscribe<String> { subscriber -> searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String?): Boolean { subscriber.onNext(newText!!) return false } override fun onQueryTextSubmit(query: String?): Boolean { subscriber.onNext(query!!) return false } }) }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text") }
      
      





これで、ログメッセージをViewModelの呼び出しに置き換えて、API呼び出しを開始できます。 ただし、これは別の記事のトピックです。







おわりに



Observable



テキスト要素をラッピングし、RxJavaを使用するこの簡単な手法を使用すると、サーバー操作の実行に必要なAPI呼び出しの数を減らし、アプリケーションの応答性を向上させることができます。 この記事では、RxJavaの全世界のごく一部しか取り上げていないため、このトピックに関する追加の記事へのリンクは残しておきます。










All Articles