Androidのライフサイクルで活きるUI周りのRxJava活用例3選

Kurimura Takahisa
Eureka Engineering
Published in
7 min readDec 13, 2017

こんにちは。Pairs JPのAndroidエンジニアの栗村貴尚(@t-kurimura)です。このAdventCalendarは、3回目の登場となります。

さて、eureka Native Advent Calendar 2017の11日目は、昨日の@yuyakaidoさんの「サポート機能でアプリ開発をより効率的に!」に続いて、RxJavaのオペレーターの話です。

このAdventCalendarの7日目で「RxJava,Kotlin,Databindingでイケてる入力フォームをスッキリ実装する」をお伝えしました。この中ではcombineLatestにを利用したフォームの入力項目の同期的なバリデーションを実装例にお伝えしました。今回の記事では、それに関連しPairs JP Android版で実際に活躍しているのUIまわりでのRxJavaのオペレーターの3つの使用例をとその実装についてご紹介します。

  1. Fragmentの切り替え時の親Activityから子Fragmentへの通知 — throttleLast
  2. スクロールで最下部到達までの監視 — distinctUntillChange
  3. ローディングのちらつき防止 — zipとtimer

Fragmentの切り替え時の親Activityから子Fragmentへの通知 — throttleLast

最近はAndroidでも多くのアプリで、メイン画面での下タブやドロワーでの画面切り替えを見るようになりました。これらの画面構成の多くの場合はすべてのタブやドロワーそのものを持つ親となるActivityがいると思います。

こうした構成の場合に、画面が特定のところに切り替わったときなどの任意のタイミングで、その画面の情報を再度読み込み直したいケースがあると思います。しかし、Androidのライフサイクル上のメソッドではFragment自身は既に生成が行われていると自分のFramgnetに切り替えられたことは気づけません。そのFragmentを持つViewを切り替えている親Activityが知らせてあげる必要があります。

このとき、子となるFragmentに、Publicメソッドを作ってActivityから呼ぶのは危険です。Fragmentが他の画面でのメモリー消費の関係などで、一度onDestroyViewが呼ばれている可能性があるからです。onDestroyViewが呼ばれていて、メンバー変数で持っているViewのプロパテイーが初期化されていると、子Fragmentへの通知からそのViewへアクセスしようとするとNullPointerExceptionが発生します。

解決策

ポイント

この解決策では、もともとActivityからFragmentへ能動的な通知を行なっていた実装を、Activityで発火しているイベントをFragment側から能動的に監視するという方法に変更しています。このことでFragmentのライフサイクルに合わせて購読を解除したり、値を捨てたりするポリシーを定めたりできます。

また、Activity側ではthrottleLastというオペレーターを使って必要以上の量の情報がFragmentに流れてしまうことを防いでいます。throttleLast(1000, TimeUnit.MILLISECONDS) は、1000ミリ秒間の間でもっとも最後にきた値をそのしたに流すオペレーターです。連打処理などにも使えますが、連打処理の場合は、連打していなくても1000ミリ秒間はユーザーを待たせてしまう点がネックです。今回の場合は、いろいろなタブの連打時、すべての画面が更新されることを防いでいます。なので、連打されている間は更新の必要がなく、最後にタップされたタブの値のみが伝わるようにしています。

throttleFirst公式ドキュメント

スクロールで最下部到達までの監視

ユースケース

Androidアプリを作る場合大抵の場合、ListView,GridView,RecyclerView,WebViewといったレイアウトを利用してスクロールを実装すると思います。長めのスクロールを実装し、最後のコンテンツに来ると「トップへ戻る」というボタンが出現するケースがあると思います。この場合、スクロールする対象のViewの高さ(Height)と現在のスクロール量(ScrollY)を監視し続けながらボタンを表示したり消したりする必要があります。

実装例

ポイント

スクロールが最下部に到達しているかどうかをスクロールのリスナーが呼ばれる度に、PublishSubjectにonNextで流しています。このままこのPublishSubjectをSubscribeすると、同じ値でも何度でも流れてしまうので、消えるアニメーションの途中で消えるアニメーションが再度始まってしまったり、その逆もまた然りです。

そこでdistinctUntilChangedというオペレーターを利用しています。この値は一つ前の値をキャッシュし、流れてきた値をその直前の値と比較して異なっていたらfilterせずに下に流しています。これを行うことによって「最下部に到達している状態」と「最下部に到達していない状態」が切り替わった時にだけ、値が流れるようにしています。

distinctUntilChanged公式ドキュメント

ローディングのちらつき防止

ユースケース

次の画面に遷移する時に確実にAPIのコールバックを受けておく必要がある処理(ユーザー登録など)では、ローディングを表示することが望ましいかと思います。しかし、そのローディングは通信制限の3Gではしっかり数秒間と表示されその意味が大きいかもしれませんが、WiFi環境では即座に処理が終了してしまったりすると、一瞬の画面変化がちらついているように見えて、親切で表示しているローディングがかえって、UXとしてはネガティブに働いてしまうともあると思います。そこで、「API通信がどれだけ早く終わっても最低1000ミリ秒は待たせるが、それ以上になった場合は通信終了を待つ」といったことがしたい場合、今回の例が有用です。ローディングで5000ミリ秒間待たせてその間に新しい機能の告知するなどもありうるケースかと思います。

実装例

ポイント

3000ミリ秒でonNextを1つ返すObservableとAPIのレスポンスを返すObservableが待ち合わせしています。双方のonNextが1つずつきて初めて下に流れます。つまり、「3000ミリ秒」と「APIのレスポンス」の遅い方のタイミングで次のページに進めます。

doOnSubscribe で購読した瞬間にローディングが表示され、 doOnDispose でonCompleteかonErrorのどちらかがきた時点でローディングが消えるようになっています。これによって、ローディングの表示によってかえってUXが低くなってしまう問題を防いでいます。

zip公式ドキュメント

おわりに

Pairs JP Android版では、API通信・DB疎通と言ったインフラ層から画面を描画するUI層まで様々な部分でRxJavaを利用しています。もちろん無下に使うと気づかぬうちにメモリーの消費量を増やしてしまうこともありますが、適切に使うことによって、Androidの複雑なライフサイクルの中でもコードの安全性を保てたり、UXを向上させることができたりします。
卑近な例でしたが、なかなかな普段使うことのないオペレーターもあったかと思うので、ご参考になれば幸いです。

明日は、Lemonさんのスクラムに関する記事です。私自身、スクラムマスターの端くれくらいではいるつもりなので、楽しみな記事です!

--

--