RxJavaを使ったAndroidにおけるエラーハンドリング
これはRxJava Advent Calendar 2016の17日目の記事です。
はじめに
みなさん、RxJava使ってますか?私は個人でも仕事でもガッツリ使っています。
この記事では、RxJavaを使ったAndroidのエラーハンドリングに関して書きたいと思います。ちなみに私は「Error Handing in RxJava」というタイトルで DroidKaigi 2017に登壇予定で、この記事は発表予定の内容をざっくりとまとめたものになります。
エラーハンドリングでよく使うOperatorの紹介
RxJavaにはエラーハンドリング用に以下のようなOperatorが定義されています。
- onError
- onErrorReturn
- onErrorResumeNext
- retry
- retryWhen
onError
これはストリーム内で投げられたExceptioinを受け取るためのOperatorです。
Observable observable = Observable.just(0); observable .map(new Func1() { @Override public Object call(Object o) { throw new RuntimeException(); } }) .subscribe(new Subscriber() { @Override public void onCompleted() {} @Override public void onError(Throwable e) {} @Override public void onNext(Object o) {} });
mapでthrowしたExceptionがonErrorに渡ってきます。
onErrorReturn
これはExceptionが投げられた場合に代わりのデータを流すためのOperatorです。
Observable<Integer> observable = Observable.just(0); observable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { throw new RuntimeException(); } }) .onErrorReturn(new Func1<Throwable, Integer>() { @Override public Integer call(Throwable throwable) { return 10; } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer integer) {} });
mapでExceptionがthrowされると、onErrorReturnが発火し、代わりのデータとして「10」が返ります。
onErrorResumeNext
これはExceptionが投げられた場合に代わりのストリームを流すためのOperatorです。
Observable<Integer> observable = Observable.just(0); observable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { throw new RuntimeException(); } }) .onErrorResumeNext(new Func1<Throwable, Observable<? extends Integer>>() { @Override public Observable<? extends Integer> call(Throwable throwable) { return Observable.just(0); } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer integer) {} });
mapでExceptionがthrowされると、onErrorResumeNextが発火し、代わりのストリームとして「Observable.just(0)」が返ります。
retry
これはExceptionが投げられた場合にリトライするためのOperatorです。
Observable<Integer> observable = Observable.just(0); observable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { throw new RuntimeException(); } }) .retry() .subscribe(new Action1<Integer>() { @Override public void call(Integer integer) {} });
mapでExceptionがthrowされると、通常はストリームが終了しますが、retryを書くことで再度subscribeされます。上記のコードは無限にリトライされるますが、リトライ回数を設定することも可能で、通常はリトライ上限を設定した方がいいでしょう。
retryWhen
retryWhenは少し分かりにくいですが、リトライするトリガーとなるストリームを返すためのOperatorです。retryの場合はExceptionが投げられた瞬間にリトライが実行されますが、retryWhenを使うことで10秒待ってからリトライするというといった具合にリトライのタイミングを細かく制御することができます。
10秒待ってからリトライするコードは以下の通りです。
Observable<Integer> observable = Observable.just(0); observable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { throw new RuntimeException(); } }) .retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() { @Override public Observable<?> call(Observable<? extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?>>() { @Override public Observable<?> call(Throwable throwable) { return Observable.timer(10, TimeUnit.SECONDS); } }); } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer integer) {} });
ユースケース
Androidアプリでは以下のようなエラーハンドリングがよく用いられます。これをRxJavaを使って実装してみたいと思います。
- Toastを表示する
- リトライするかをユーザーに尋ねる
Toastを表示する
これは簡単ですね。onErrorでToastを表示するだけです。
Observable observable = Observable.just(0); observable .map(new Func1() { @Override public Object call(Object o) { throw new RuntimeException(); } }) .subscribe(new Subscriber() { @Override public void onCompleted() {} @Override public void onError(Throwable e) { Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_SHORT).show(); } @Override public void onNext(Object o) {} });
リトライするかをユーザーに尋ねる
これは先程紹介したretryWhenを使います。retryWhen内でSnackbarを用いてユーザーにリトライするかどうかを尋ねます。
Observable<Integer> observable = Observable.just(0); observable .map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { throw new RuntimeException(); } }) .retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() { @Override public Observable<?> call(Observable<? extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?>>() { @Override public Observable<?> call(Throwable throwable) { return Observable.create(new Observable.OnSubscribe<Void>() { @Override public void call(final Subscriber<? super Void> subscriber) { Snackbar snackbar = Snackbar.make(view, "Retry?", Snackbar.LENGTH_INDEFINITE); snackbar.setAction("Yes", new View.OnClickListener() { @Override public void onClick(View view) { subscriber.onNext(null); } }); snackbar.show(); } }); } }); } }) .subscribe(new Action1<Integer>() { @Override public void call(Integer integer) {} });
まとめ
今回は以下のようなエラーハンドリング用のOperatorを紹介しました。
- onError
- Exceptionを受け取ることができる
- onErrorReturn
- Exceptioinが投げられた場合に代わりのデータを流すことができる
- onErrorResumeNext
- Exceptionが投げられた場合に代わりのストリームを流すことができる
- retry
- Exceptionが投げられた場合にリトライすることができる
- retryWhen
- リトライのタイミングを細かく制御することができる
上記のOperatorを踏まえて、Androidでよくあるエラーハンドリングの実装例を紹介しました。
- Toastを表示する
- onErrorでToastを表示する
- リトライするかどうかをユーザーに尋ねる
- retryWhenとSnackbarを組み合わせる
この他にもコメントなどいただければ実装例を追記していきたいと思います。
それでは、良いRxJavaライフを!