retrofit2での通信エラー処理
通信していると、エラー処理をする必要が出てくる。retrofit2の場合、callbackにonFailureというメソッドがあるけど、これは基本的に電波状態が良くなくてそもそもリクエストを送れなかったときとかに呼ばれる。なので、4xx系エラーとか5xx系エラーの処理はこのonFailureではできない。ステータスコードを確認するにはonResponseの中でisSuccessful()とか使わないといけない。が、onResponseに来た時点で、jsonはcallbackを定義した時のインスタンスに変換されちゃっている。
Call<User> call = service.getUser(); call.enqueue(new Callback<User>() { @Override public void onResponse(Call<User> call, Response<User> response) { if (response.isSuccessful()) { // do something } else { // 通信エラー } } });
こういう場合、onResponseの中ではjsonの結果はステータスコードによらず、Userオブジェクトインスタンスに変換されている。jacksonなどをconverterに使っているならJsonIgnoreProperties(ignoreUnknown = true)とかのアノテーション指定してエラー内容を無視するとか、Userインスタンスにエラー時のエラー内容を格納するプロパティ用意しとけばいいのかもしれないけど、それも面倒だ。通信で変換するクラス全てにエラー用フィールドを入れたくはない。
で、こういう時にもっと汎用的なエラーインスタンスを作りたい。isSuccessful()でelse節に飛んだ時、UserインスタンスじゃなくてAPIErrorクラスインスタンスとかもっと汎用的に使えるオブジェクトに変換して欲しい。そういう場合の手法。
エラーエンティティ
何にしてもUserエンティティのような汎用的エラーエンティティが必要になるので、それをまず定義する。
public class APIError { private int statusCode; private String message; public APIError() { } public int status() { return statusCode; } public String message() { return message; } }
簡単にエラーのステータスコードと、エラー内容文字列が取れるようにしているだけ。
エラーハンドラ
で、あとはそのrestfitのresponseオブジェクトをエラーエンティティに変換するものが必要になる。
public class ErrorUtils { public static APIError parseError(Response<?> response) { Converter<ResponseBody, APIError> converter = ServiceGenerator.retrofit() .responseBodyConverter(APIError.class, new Annotation[0]); APIError error; try { error = converter.convert(response.errorBody()); } catch (IOException e) { return new APIError(); } return error; } }
response.errorBody()でエラー内容を取得できるので、それをresponseBodyConverterで変換。responseBodyConverterの第一引数は変換結果としたいエラーエンティティのクラス。第二引数はちょっと調べてみたけどよくわからなかった。でもnew Annotation[0]のままで一切変えずともうまく行った。
というわけで、あとはこのstaticなメソッドをonResponseの中で呼び出してやればいい。
参考: