RxAndroidでダブルクリックイベントをキャプチャする
2週ぐらい遅れてる感はあるが巷で話題のReactiveProgrammingなるものに入門してみたので導入方法と簡単なサンプルを備忘録として残しておきます。今回試したのはRxAndroidです。
Rxにとりあえず入門してみる
そもそも「Rxとはなんぞや」というところがわかってないとどうしようもないので色々と調べた結果、今のところは以下のように理解しました。
- 全てのデータをストリームとして扱う
- ストリームからは任意のタイミングでイベントが発行される
- ストリームを監視することで、イベントベースで宣言的に処理が記述できるようになる
- 宣言的な記述によって、状態に依存した複雑な分岐処理等を書かなくてよくなる(=バグの混入が減る)
ざっくりしすぎな感はありますが基本的なところはおおむね合ってるんじゃないかと思います。Rxの用語でまとめると「ObservableなストリームをSubscribeして、SubscriberにPushされてくるイベントにOperatorを適用してイイ感じにデータフローをハンドリングする」という感じでしょうか。
色々な記事を参考にさせてもらいましたが、特に以下の記事が分かりやすかったです。
【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun's diary
RxAndroidをカジュアルに使ってみるとか - みんからきりまで
RxAndroidを導入する
RxAndroidはRxJavaにAndroid用のコンポーネントを追加したRxJavaの拡張ライブラリです。2016/04/25現在の最新バージョンは1.1.0です。
公式ページを参照してbuild.gradleに以下の記述を追加します。
compile 'io.reactivex:rxandroid:1.1.0' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. compile 'io.reactivex:rxjava:1.1.3'
ダブルクリックイベントを発行するストリームを作成する
Rxの基本的な概念が分かったところで、先ほど参考にあげたページで例に出ていた「ダブルクリックイベントをキャプチャする」という処理をRxAndroidで実際に書いてみます。
なおRxAndroid1.0以前はViewObservabel
というクラスを使ってAndroidのウィジェットのイベントストリームを作成していたようですが、現在はウィジェット関連はRxBindingという別ライブラリに切り出されているため、RxAndroidとは別にbuild.gradleに追加してやる必要があります。
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0' compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0' compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.4.0'
今回は画面要素としてButtonとダブルクリックされた回数を表示するTextViewだけがあるシンプルなアプリを実装してみます。 準備ができたら以下の流れで実装します。
- クリックイベントを発行するObservableを作成する
- クリックされたら1を返すように変換する
- イベント発行回数を1秒単位でまとめる
- 「1秒以内に2回クリックされた」イベントを抽出する
- 上記のイベント発生回数を累計して画面に表示する
実際の処理は以下のようになります。
1つ1つの処理の意味は以下のとおりです。
map
map
の引数にFunctionインタフェースを実装したメソッドクラス(ここではFunc1)を渡すと、Subscribeしたイベントに対してメソッドクラスのcall
が実行されます。callで返す値が次のメソッドの引数に渡されます。
ここではボタンがクリックされたら単純に1を返しています。
doubleClickObservable.map(new Func1<Void, Integer>() { @Override public Integer call(Void aVoid) { // クリックされたら1を返すように設定 return 1; } })
buffer
buffer
では時間を指定することで、指定した時間内に発生したイベントをリストにまとめることができます。ここでは1秒間に発生したイベントを次のメソッドに渡しています。
.buffer(1, TimeUnit.SECONDS) // イベントをまとめる
map(イベント発生回数を取得)
ここではmapを使って、bufferから受け取ったイベントの個数をイベント発生回数に変換しています。ここで変換した値が次のメソッドに渡されます。
.map(new Func1<List<Integer>, Integer>() { @Override public Integer call(List<Integer> integers) { // イベント発生回数をfilterに送る Log.d("AAA", integers.size() + "times clicked in a second"); return integers.size(); } })
filter
filter
は引数の値に何らかの判定処理を行い、true/falseを判定することで次のメソッドに渡す値をフィルタリングします。filterでfalseになった値は単に捨てられてSubscriberまで通知されません。
ここでは1つ前のmapから受け取ったイベント発生回数が2回であるかを判定しています。
.filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer clicked) { // 1秒間のイベント発生回数が2回の場合のみ抽出する(ダブルクリックの検出) boolean isDoubleClicked = clicked == 2; Log.d("AAA", "isDoubleClicked : " + isDoubleClicked); return isDoubleClicked; } })
map(ダブルクリック回数をカウント)
上記のfilterまででダブルクリックの検出は完了です。このmapが実行されるのはダブルクリックを検出した時だけなので、ダブルクリックの発生を1回カウントします。ここでは1を返すだけです。
.map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { // ダブルクリックを検出したら1を返す return 1; } })
scan
scan
は現在までのイベント発生回数を累計できます。scanが実行されるたびに、現在までの合計に1つ前のmapの値を加算します。
.scan(new Func2<Integer, Integer, Integer>() { @Override public Integer call(Integer acc, Integer count) { // これまでの処理結果を累積してObserverへ通知する return acc + count; } })
observeOn
observeOn
でObservableの監視を行うSchedulerを指定することができます。今回はこの位置でAndroidSchedulers.mainThread()
を指定しないとWrongThreadExceptionでエラーが発生するという状態になったのでここで指定しています(が、このあたりはまだよくわかっていない)。
.observeOn(AndroidSchedulers.mainThread())
subscribe
subscribe
を実行することでObservable側のイベント通知が実行されるようになります。subscribeを実行するまではボタンをクリックしてもイベントは通知されません。
監視対象のObservableからイベントが通知されてきたら、subscribeに渡しているActionインタフェースのインスタンスのonNext
が実行されます。
今回はここでダブルクリックされた回数を示すラベルを更新しています。ダブルクリック以外では値が増加しないことを確認してみてください。
.subscribe(new Action1<Integer>() { @Override public void call(Integer count) { Log.d("AAA", "Double clicked : count = " + count); mLabel.setText(Integer.toString(count)); } });
ちなみに以下のページが実装の参考になりました。
rx java - RxJava - how to invoke subscriber on 4 click events in Android - Stack Overflow
まとめ
以上、ざっくりとですがRxAndroidの使い方とRxの基本的な概念をおさらいしてみました。概念を把握して実装に落としこむのに少し苦労しましたが、使いこなせればコードが簡潔かつ宣言的に記述できるようになりそうなので、かなり便利そうな気配を感じています。
ただしラムダが使えないとサンプルコードみたいに匿名クラスまみれになってしまってつらみしか感じない残念な感じになるので、Java8かRetrolambdaの導入はほぼ必須だと思います。
もしくはKotlinが使える環境*1ならこれを機にKotlinに本格移行を検討してもよいんじゃないかと思いました。
*1:仕事的な意味も含めて