techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

RxJava:doOnEach系メソッドでObservableのライフサイクルを理解してデバッグに役立てる

今回はRxJavaのdo〜系メソッドを使ってObservableのデバッグに役立てる方法を紹介します。

出力元Observableの全出力をチェックしたい

前回前々回の記事でRxJavaのサンプルをいくつか作成しましたが、その時に微妙に困ったのが「Observableからの全ての出力をチェックしたいが実際に値が出力されるのはSubscribe後のObservableなので値がフィルタリングされてしまいフィルタリング後の値しかわからない」ということです。

よくあるデバッグ手法としてループ実行時に全ての値を出力しながら特定の条件の場合はif文の中でログを出力するということをやりますが、Subscribe後のObservableというのは上記のif文の中に相当するので、条件に該当しない値が出力されず微妙にデバッグしづらいなーと感じていました。

イメージとしては以下のコードのような感じです。

for (int i = 0; i < 10; i++) {
    // ここも見たい
    if (i % 2 == 0) {
        // ここしか見れない
        Log.d("AAA", "i = " + i);
    }
}

何とか全ての値をログに出力しつつObservableとしては適切にフィルタリングされたものをSubscribeできないかなー*1と思い公式ページを眺めているとdoOnEachというものがありました。このdo〜系のメソッドを使うことでObservableで発生する各イベントに対して、Observable自体には影響を与えずにコールバックを登録することができます。

doOnEach系のメソッドでコールバックを登録する

doOnEachなどのコールバック登録メソッドは公式ページではDoオペレータとして定義されています。

Doオペレータで登録した各メソッドが、Observableのライフサイクルイベント発生時にコールバックとして実行されます。公式ページでRxJavaのDoオペレータとして記載されているのは以下のとおりです。

  1. doOnEach
  2. doOnNext
  3. doOnRequest*2
  4. doOnSubscribe
  5. doOnUnsubscribe
  6. doOnCompleted
  7. doOnError
  8. doOnTerminate
  9. finallyDo(deprecated)
  10. doAfterTerminate(finallyDoの代わりに追加)

これらのメソッドを試してみると以下のような結果となります。

Observable.range(0, 10).doOnEach(notification -> {
    Log.d("AAA", "doOnEach : kind = " + notification.getKind() + ", value = " + notification.getValue());
}).doOnCompleted(() -> {
    Log.d("AAA", "doOnCompleted");
}).doOnTerminate(() -> {
    Log.d("AAA", "doOnTerminate");
}).doAfterTerminate(() -> {
    Log.d("AAA", "doAfterTerminate");
}).doOnSubscribe(() -> {
    Log.d("AAA", "doOnSubscribe");
}).filter(i -> i % 3 == 0).subscribe(i -> {
    Log.d("AAA", "filtered : " + i);
});
// doOnSubscribe
// doOnEach : kind = OnNext, vallue = 0
// filtered : 0
// doOnEach : kind = OnNext, vallue = 1
// doOnEach : kind = OnNext, vallue = 2
// doOnEach : kind = OnNext, vallue = 3
// filtered : 3
// doOnEach : kind = OnNext, vallue = 4
// doOnEach : kind = OnNext, vallue = 5
// doOnEach : kind = OnNext, vallue = 6
// filtered : 6
// doOnEach : kind = OnNext, vallue = 7
// doOnEach : kind = OnNext, vallue = 8
// doOnEach : kind = OnNext, vallue = 9
// filtered : 9
// doOnEach : kind = OnCompleted, vallue = null
// doOnCompleted
// doOnTerminate
// doAfterTerminate

上記のコードを見ると、実際にSubscribeしているのはfilter(i -> i % 3 == 0)の値だけですが、出力元Observableからの全ての出力とObservableの各イベント発生タイミングが取得できていることがわかります。これならそれぞれの情報を使ってデバッグに役立てることができそうです。

また、それぞれのメソッドはもちろんデバッグ用途だけではなく、「Observableからの出力は全て取得したいけどUIの更新は一定間隔ごとに行いたい」などの用途にも使えるんじゃないかと思います。

*1:全ての値をログ出力するだけなら単純にmapやらfilterを使えば望み通りの挙動になりますが、それではそれぞれのメソッドの趣旨と違うんじゃないかと思ったのでここでは考慮しないことにしました

*2:back-pressureのデバッグ用らしいがよくわからないので割愛