2015-11-16 10 views
12

ObservableインターフェイスとResultラッパーを使用してプロジェクトでRetrofit 2を使用しています。例:Retrofit 2とRxJavaのエラー処理オペレータ

@POST("api/login") 
Observable<Result<LoginResponse>> login(@Body LoginRequest request); 

私はシリアライズされたオブジェクト(例えば、ヘッダー、HTTPステータス...)よりも応答からより多くの情報を得るために、結果のラッパーを必要とします。

結果ラッパーでは、ネットワーク呼び出しによって例外がスローされません。 Result.error()を呼び出すことで、結果内で例外を見つけることができます。

RxJavaエラー演算子を利用するにはどうすればよいですか? たとえば、ネットワークエラーで再試行演算子を使用したいが、再試行演算子は、observableによって例外がスローされた場合にのみ機能します。

+0

リンクを参照してください。http://bytes.babbel.com/en/articles/2016-03-16-retrofit2-rxjava-error-handling.html _Retrofit.Builder_の_RxJavaCallAdapterFactory_をオーバーライドする方法について説明しています。 _ Observable.onResumeErrorNext_/_Observable.onDoNext()_ – murt

答えて

13

ここに私が思いついた解決策があります。 私はそれを改善するつもりならば、私はここに変更を掲載します。

私の問題を解決するには、(例外は改修によって飲み込まれRxJavaによって処理されていない)だけで、エラーを発するので、私は例外を「再スロー」ができることを新たに観察できるが作成されますObservable.error方法です。

私はretrofit.Resultを出すすべての残りの呼び出しに追加する観測可能なトランスフォーマを作成しました。 このトランスフォーマはObservable>をとり、エラーがなければObservable>に変換します。エラーがある場合、カスタムHttp * Exceptionsを持つObservable.errorを返します。これは、後でオブザーバーでonErrorコールバックで処理できます。 ObservableTransformations.resultToResponseWithHttpErrorHandlingというユーティリティクラスの静的メソッドとして記述します。ここ

それは:

public class ObservableTransformations { 

public static <T> Observable.Transformer<Result<T>, Response<T>> resultToResponseWithHttpErrorHandling() { 
    return observable -> observable.flatMap(r -> { 
     Observable<Response<T>> returnObservable = Observable.just(r.response()); 
     if (r.isError()) { 
      Throwable throwable = r.error(); 
      if (throwable instanceof IOException) { 
       Timber.e(throwable, "Retrofit connection error."); 
       // TODO Check this cases 
       if (throwable instanceof java.net.ConnectException) { 
        returnObservable = Observable.error(new HttpNoInternetConnectionException()); 
       } else if (throwable instanceof SocketTimeoutException) { 
        returnObservable = Observable.error(new HttpServerDownException()); 
       } else { 
        returnObservable = Observable.error(new HttpNoInternetConnectionException()); 
       } 
      } else { 
       Timber.e(throwable, "Retrofit general error - fatal."); 
       returnObservable = Observable.error(new HttpGeneralErrorException(r.error())); 
      } 
     } else { 
      Response<T> retrofitResponse = r.response(); 
      if (!retrofitResponse.isSuccess()) { 
       int code = retrofitResponse.code(); 
       String message = ""; 
       try { 
        message = retrofitResponse.errorBody().string(); 
       } catch (IOException e) { 
        Timber.e(e, "Error reading errorBody from response"); 
       } 
       Timber.i("Server responded with error. Code: " + code + " message: " + message); 
       Throwable t = null; 
       if (NetworkUtils.isClientError(code)) { 
        t = new HttpClientException(retrofitResponse.code(), message); 
       } else if (NetworkUtils.isServerError(code)) { 
        t = new HttpServerErrorException(retrofitResponse.code(), message); 
       } 
       returnObservable = Observable.error(t); 
      } 
     } 
     return returnObservable; 
    }).retryWhen(new RetryWithDelayIf(3, 1000, t -> { 
     return (t instanceof HttpNoInternetConnectionException) || (t instanceof HttpServerDownException); 
    })); 
} 

} 

リトライは、指数バックオフを使用して3回行われ、例外がHttpNoInternetConnectionException又はHttpServerDownException場合のみ。

RetryWithDelayIfクラスはこちらです。再試行の条件は、コンストラクタの最後の引数(throwableを取る関数で、このthrowableが再試行をトリガする必要がある場合はtrue、そうでない場合はfalseを返す関数)が必要です。あなたの観察者ののonError

restService.login(new LoginRestRequest(username, password)) 
       .compose(ObservableTransformations.resultToResponseWithHttpErrorHandling()); 

はあなたが最終的にHTTPは*例外を処理することができます:

public class RetryWithDelayIf implements 
    Func1<Observable<? extends Throwable>, Observable<?>> { 

private final int maxRetries; 
private final int retryDelayMillis; 
private int retryCount; 
private Func1<Throwable, Boolean> retryIf; 

public RetryWithDelayIf(final int maxRetries, final int retryDelayMillis, Func1<Throwable, Boolean> retryIf) { 
    this.maxRetries = maxRetries; 
    this.retryDelayMillis = retryDelayMillis; 
    this.retryCount = 0; 
    this.retryIf = retryIf; 
} 

@Override 
public Observable<?> call(Observable<? extends Throwable> attempts) { 
    return attempts.zipWith(Observable.range(1, maxRetries + 1), (n, i) -> { 
     return new Tuple<Throwable, Integer>(n, i); 
    }) 
      .flatMap(
        ni -> { 
         if (retryIf.call(ni.getFirst()) && ni.getSecond() <= maxRetries) { 
          return Observable.timer((long) Math.pow(2, ni.getSecond()), TimeUnit.SECONDS); 
         } else { 
          return Observable.error(ni.getFirst()); 
         } 
        }); 
} 

} 

最後に、ここでrestServiceコールでの使用です。

+0

私の問題に役立つ解決策のように見えます。私の質問を参照してください。多分、コミュニティの利益のために素晴らしい汎用ソリューションを考え出すことができます。 http://stackoverflow.com/questions/39437299/rxjava-retrofit-baseobservable-for-api-calls-for-centralized-response-handl – GuyZ

0

スローされたThrowableがHttpExceptionのインスタンスであるかどうかを確認する必要があります。

+0

あなたのオブジェクトをキャッチする可能性があります申し訳ありませんが、私はあなたが問題を理解していないと思う。ところで、Retrofit 2は、サーバーに到達できない場合(インターネット接続なし、サーバーダウン..)、一般的なIOExceptionをスローします。 – Ena

関連する問題