2015-11-16 17 views
12

Uso Retrofit 2 nel mio progetto con l'interfaccia Observable e il wrapper Result. Esempio:Operatori di gestione degli errori Retrofit 2 e RxJava

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

ho bisogno l'involucro Risultato di ottenere maggiori informazioni dalla risposta che solo l'oggetto serializzato (ad esempio intestazioni, di stato http ...).

Il problema è che con il wrapper Result non viene generata alcuna eccezione dalla chiamata di rete. È possibile trovare l'eccezione all'interno del risultato chiamando Result.error().

Cosa devo fare se voglio sfruttare gli operatori di errore RxJava? Ad esempio, mi piacerebbe utilizzare l'operatore di tentativi su un errore di rete, ma l'operatore di nuovo tentativo funziona solo se un'eccezione viene lanciata dall'osservabile.

+0

Vedere il collegamento http://bytes.babbel.com/en/articles/2016-03-16-retrofit2-rxjava-error-handling.html Descrivono come eseguire l'override di _RxJavaCallAdapterFactory_ in _Retrofit.Builder_ e lì per potresti catturare i tuoi oggetti in _Observable.onResumeErrorNext_/_Observable.onDoNext() _ – murt

risposta

13

Ecco la soluzione che ho trovato. Se lo migliorerò, posterò le modifiche qui.

La soluzione al mio problema (eccezione inghiottito dal retrofit e non gestito da RxJava) è il metodo Observable.error che crea una nuova osservabile che solo emette l'errore, così posso "rigenerare" l'eccezione.

Ho creato un trasformatore osservabile da aggiungere ad ogni chiamata di riposo che emette un retrofit.Risultato. Questo trasformatore prende un Osservabile> e, se la risposta non ha errori, lo trasforma in Osservabile>. Se ci sono errori restituisce un Observable.error con Http personalizzato * Eccezioni che posso successivamente gestire nel mio Observer nel callback onError. L'ho messo come metodo statico di una classe di utilità chiamata ObservableTransformations.resultToResponseWithHttpErrorHandling.

Qui è:

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); 
    })); 
} 

} 

La tentativi è fatto 3 volte utilizzando un backoff esponenziale, e solo se l'eccezione è HttpNoInternetConnectionException o HttpServerDownException.

La classe RetryWithDelayIf è qui. Prende la condizione per essere soddisfatta per riprovare come l'ultimo argomento del costruttore (una funzione che prende un throwable e restituisce true se questo throwable deve attivare il tentativo e false se non lo è).

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()); 
         } 
        }); 
} 

} 

Infine, ecco l'utilizzo con una chiamata restService:

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

Nel onError del vostro osservatore si può finalmente gestire HTTP * Eccezioni.

+0

Sembra una soluzione che potrebbe essere utile per il mio problema.Si prega di vedere la mia domanda e forse possiamo trovare una soluzione generica a beneficio della comunità. http://stackoverflow.com/questions/39437299/rxjava-retrofit-baseobservable-for-api-calls-for-centralized-response-handl – GuyZ

0

Controllare se il Throwable generato è un'istanza di HttpException.

+0

Mi dispiace ma penso che tu non abbia capito il problema. A proposito, Retrofit 2 ora genera una IOException generica se non riesce a raggiungere il server (nessuna connessione Internet, server inattivo ..). – Ena