注意!
この記事はQiitaにて公開されていた内容をimportしたものです。
これらの内容は場合によっては陳腐化していて役に立たなくなっていたり、有害であったり、現在の著者の主張と異なることがあります。
皆様の判断の上でご利用いただけますと幸いです(度を超してヤバいものは著者に連絡して頂ければ対応します m(_ _)m)
はじめに
みなさんRxしてますか?(挨拶)
Androidアプリ開発で通信が必要な処理を実装するにあたり、Retrofitを採用して開発することが多いかと思います。Retrofitは3月にメジャーバージョンアップを果たし、色々とマイグレーション作業に追われた方もおられるかと思います。お恥ずかしいことに僕はRetrofit2になってからちゃんとRetrofitを使い始めたのですが、心底「もっと早くに使っておけば・・・」と思いました。へへっ
Retrofitは、それ単体では返り値の型定義としてCallcom.squareup.retrofit2:adapter-rxjava:2.+
)を使い、RxJavaのStreamとして結果を受け取っています(あとGson Converterも使いますが本筋と関係ないので省略)。
// ナマ ※本記事ではこのように表現しますが、良い呼び方があれば教えて下さい
@GET("/api/users")
Observable<List<User>> users();
ところで、rx.Observable<T>
に指定するTですが、Converterにマッピングして欲しいクラス(上記の例で言えば、List<User>
クラス)を指定する以外にも、以下のようにretrofit2.Response<T>
やretrofit2.adapter.rxjava.Result<T>
でラップして定義することも出来ます。
// Response<T>
@GET("/api/users")
Observable<Response<List<User>>> usersResponse();
// Result<T>
@GET("/api/users")
Observable<Result<List<User>>> usersResult();
この3種類の使い分けをどうすればいいのかがイマイチよく分からなかったのと、わからないなりに適当に使っていたら案の定事故ってしまったので、反省しつつ色々と検証をしてみて整理してみました。
前提知識
Response<T>
について
retrofit2.Response<T>
はretrofit2に含まれるクラスで、主にokhttp3.Response
をラップしているものです。
Response#isSuccessful()
は、[200..300)
(ステータスコード200番以上300番未満)のときtrueで、
Response#body()
にTで指定したオブジェクトが入っています。falseのときはResponse#errorBody()
にokhttp3.ResponseBody
なデータが入っています(body()はnullになるので注意)。エラー時にJSON等で理由を知らせてくれるAPIがありますが、そのときのデータはerrorBody()から取り出せば良いということです。ただしConverter等で変換されているわけではないので、自前でパースする必要があります。
Result<T>
について
retrofit2.adapter.rxjava.Result<T>
は、adapter-rxjavaに含まれるクラスで、先ほど説明したResponse<T>
、それからThrowable
をラップしています。
Result#isError()
は、Throwable
を持っていればtrueを返します。このときResult#response()
はnullなので注意。
ナマ、Response<T>、Result<T>の違い
結論から言うと、APIレスポンス(あるいはRetrofitで処理中に生じたException)をonNextとして流すかonErrorとして流すかが異なります。
ナマ | Response<T> | Result<T> | |
---|---|---|---|
200 | onNext | onNext | onNext |
404 | onError | onNext | onNext |
壊れたJSON | onError | onError | onNext |
Subscriber#onNext() でのやらかし |
onError | onError | onError |
ナマ(Gson等でパースして欲しいオブジェクトだけをObservable<T>に指定した場合)は、リクエストが成功しかつオブジェクトを正しく取得・変換出来た場合のみonNextが呼ばれます。それ以外はすべてonErrorとして扱われます。なお、404エラーなどのレスポンス異常系もretrofit2.adapter.rxjava.HttpException
としてスローされonErrorとして流れてきます。
Response<T>を指定した場合は、レスポンスが帰って来たことに関してはonNextとして処理されますが、UnknownHostException等Exceptionがどこかで発生するとonErrorとして扱われます。つまり、レスポンスが404エラーや500エラーの時はonNextとして処理されますが、圏外・機内モード等で接続ができなかったときはExceptionが呼ばれるのでonErrorとして扱われます。あとJSONパース失敗時もonErrorです。
Result<T>を指定した場合は、Retrofitでの処理からSubscriberに渡すまでのところで起きたいかなるExceptionも包み込んでonNext()で処理されます。ただし、onNext内でミスをする(例えばIndexOutOfBoundsExceptionとかNullPointerExceptionとか)と、そこからonErrorに渡りますのでご注意を。
これらの使い分けはどうすればいいのか?
これが結構悩ましい、ということで自分もこれだ!という使い分けを思い至っていないのですが。。。
個人的には、レスポンスが無事帰って来たものはonNextとして受け取りたい気持ちがあるので、Response<T>
が好みです。ステータスコードがAPIの結果だ、というようなAPIもよくあるので、それらはonNextで処理しつつ、onErrorではExceptionの種類をinstanceof等で比較してダイアログを出し分けるコードを書いておく、という感じにしておくと、コードの整理もしやすいかな、と。
そのような考えでいくとResult<T>
はonNext内でごちゃごちゃと書く必要があります。onNextでisError()がtrueのときは・・・とかやりたくないというか、onNextは正常系しか来ないよ!ってことにしておきたい気持ちがあります。
そういう意味では、「どこまでを正常系として扱うか」という意味で使い分けるのが良いのでは?という気持ちです。
「オレはこうしているぜ!」ってのがありましたらコメント欄で教えていただけると嬉しいです!
おまけ: Single<T>
vs Observable<T>
@GET("/api/users")
Single<List<User>> users();
上記のように書くことで、Observable#subscribe()
のときにSingleSubscriber
を引数として渡すと、onSuccess(T)
かonError(Throwable)
のみコールバックとして受け取れるので、「Subscriber#onSuccess()
いらねーんだけどなー・・・」って時に見通しがよくなりキモチイイ!
ただ、以下のようにすればObservableを返す定義であってもSingleSubscriberで受け取れるので、僕は基本的にObservable<T>
で定義を書いています(あと仕様変更で急に並列リクエストして待つような処理に書き換えたい時とかに予めSingleだとそれが出来ないんじゃない?と_試しもせずに_思っています…)。
api.users()
.observeOn(AndroidSchedulers.mainThread())
.toSingle()
.subscribe(new SingleSubscriber<Result<List<User>>>(){ /* ... */});
ただこのあたりも他所様がどうしているかとかが分からないので、わざと自分がどうしているかを公開して各位からのご意見を頂きたいなーなどと思い公開しておきます。。