-
-
Save jdsingh/43076f9052295cc1c7985edb2c1e0aed to your computer and use it in GitHub Desktop.
Retrofit 1 error handling behaviour in Retrofit 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Dagger 1 example | |
@Module( | |
complete = false, | |
library = true | |
) | |
public final class ApiModule { | |
@Provides | |
@Singleton | |
Retrofit provideRetrofit(Gson gson, Application app) { | |
return new Retrofit.Builder() | |
.baseUrl(app.getString(R.string.base_url)) | |
.addConverterFactory(GsonConverterFactory.create(gson)) | |
.addCallAdapterFactory(new ErrorHandlingExecutorCallAdapterFactory(new ErrorHandlingExecutorCallAdapterFactory.MainThreadExecutor())) | |
.build(); | |
} | |
@Provides | |
@Singleton | |
LoginService provideLoginService(Retrofit retrofit) { | |
return retrofit.create(LoginService.class); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Modified version of https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java | |
public class ErrorHandlingExecutorCallAdapterFactory implements CallAdapter.Factory { | |
private final Executor callbackExecutor; | |
ErrorHandlingExecutorCallAdapterFactory(Executor callbackExecutor) { | |
this.callbackExecutor = callbackExecutor; | |
} | |
@Override | |
public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { | |
if ($Gson$Types.getRawType(returnType) != Call.class) { | |
return null; | |
} | |
final Type responseType = getCallResponseType(returnType); | |
return new CallAdapter<Call<?>>() { | |
@Override | |
public Type responseType() { | |
return responseType; | |
} | |
@Override | |
public <R> Call<R> adapt(Call<R> call) { | |
return new ExecutorCallbackCall<>(callbackExecutor, call); | |
} | |
}; | |
} | |
static final class ExecutorCallbackCall<T> implements Call<T> { | |
private final Executor callbackExecutor; | |
private final Call<T> delegate; | |
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) { | |
this.callbackExecutor = callbackExecutor; | |
this.delegate = delegate; | |
} | |
@Override | |
public void enqueue(Callback<T> callback) { | |
delegate.enqueue(new ExecutorCallback<>(callbackExecutor, callback)); | |
} | |
@Override | |
public Response<T> execute() throws IOException { | |
return delegate.execute(); | |
} | |
@Override | |
public void cancel() { | |
delegate.cancel(); | |
} | |
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone. | |
@Override | |
public Call<T> clone() { | |
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone()); | |
} | |
} | |
static final class ExecutorCallback<T> implements Callback<T> { | |
private final Executor callbackExecutor; | |
private final Callback<T> delegate; | |
ExecutorCallback(Executor callbackExecutor, Callback<T> delegate) { | |
this.callbackExecutor = callbackExecutor; | |
this.delegate = delegate; | |
} | |
@Override | |
public void onResponse(final Response<T> response, final Retrofit retrofit) { | |
if (response.isSuccess()) { | |
callbackExecutor.execute(new Runnable() { | |
@Override | |
public void run() { | |
delegate.onResponse(response, retrofit); | |
} | |
}); | |
} else { | |
callbackExecutor.execute(new Runnable() { | |
@Override | |
public void run() { | |
delegate.onFailure(RetrofitException.httpError(response.raw().request().urlString(), response, retrofit)); | |
} | |
}); | |
} | |
} | |
@SuppressWarnings("ThrowableResultOfMethodCallIgnored") | |
@Override | |
public void onFailure(final Throwable t) { | |
RetrofitException exception; | |
if (t instanceof IOException) { | |
exception = RetrofitException.networkError((IOException) t); | |
} else { | |
exception = RetrofitException.unexpectedError(t); | |
} | |
final RetrofitException finalException = exception; | |
callbackExecutor.execute(new Runnable() { | |
@Override | |
public void run() { | |
delegate.onFailure(finalException); | |
} | |
}); | |
} | |
} | |
public static class MainThreadExecutor implements Executor { | |
private final Handler handler = new Handler(Looper.getMainLooper()); | |
@Override | |
public void execute(@NonNull Runnable r) { | |
handler.post(r); | |
} | |
} | |
static Type getCallResponseType(Type returnType) { | |
if (!(returnType instanceof ParameterizedType)) { | |
throw new IllegalArgumentException( | |
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>"); | |
} | |
final Type responseType = getSingleParameterUpperBound((ParameterizedType) returnType); | |
// Ensure the Call response type is not Response, we automatically deliver the Response object. | |
if ($Gson$Types.getRawType(responseType) == retrofit.Response.class) { | |
throw new IllegalArgumentException( | |
"Call<T> cannot use Response as its generic parameter. " | |
+ "Specify the response body type only (e.g., Call<TweetResponse>)."); | |
} | |
return responseType; | |
} | |
public static Type getSingleParameterUpperBound(ParameterizedType type) { | |
Type[] types = type.getActualTypeArguments(); | |
if (types.length != 1) { | |
throw new IllegalArgumentException( | |
"Expected one type argument but got: " + Arrays.toString(types)); | |
} | |
Type paramType = types[0]; | |
if (paramType instanceof WildcardType) { | |
return ((WildcardType) paramType).getUpperBounds()[0]; | |
} | |
return paramType; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Retrofit retrofit = new Retrofit.Builder() | |
.baseUrl("https://api.github.com") | |
.addConverterFactory(GsonConverterFactory.create(gson)) | |
.addCallAdapterFactory(new ErrorHandlingExecutorCallAdapterFactory(new ErrorHandlingExecutorCallAdapterFactory.MainThreadExecutor())) | |
.build(); | |
GitHubService service = retrofit.create(GitHubService.class); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is RetrofitError converted to Retrofit 2 | |
public class RetrofitException extends RuntimeException { | |
public static RetrofitException httpError(String url, Response response, Retrofit retrofit) { | |
String message = response.code() + " " + response.message(); | |
return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit); | |
} | |
public static RetrofitException networkError(IOException exception) { | |
return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null); | |
} | |
public static RetrofitException unexpectedError(Throwable exception) { | |
return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null); | |
} | |
/** Identifies the event kind which triggered a {@link RetrofitException}. */ | |
public enum Kind { | |
/** An {@link IOException} occurred while communicating to the server. */ | |
NETWORK, | |
/** A non-200 HTTP status code was received from the server. */ | |
HTTP, | |
/** | |
* An internal error occurred while attempting to execute a request. It is best practice to | |
* re-throw this exception so your application crashes. | |
*/ | |
UNEXPECTED | |
} | |
private final String url; | |
private final Response response; | |
private final Kind kind; | |
private final Retrofit retrofit; | |
RetrofitException(String message, String url, Response response, Kind kind, Throwable exception, Retrofit retrofit) { | |
super(message, exception); | |
this.url = url; | |
this.response = response; | |
this.kind = kind; | |
this.retrofit = retrofit; | |
} | |
/** The request URL which produced the error. */ | |
public String getUrl() { | |
return url; | |
} | |
/** Response object containing status code, headers, body, etc. */ | |
public Response getResponse() { | |
return response; | |
} | |
/** The event kind which triggered this error. */ | |
public Kind getKind() { | |
return kind; | |
} | |
/** The Retrofit this request was executed on */ | |
public Retrofit getRetrofit() { | |
return retrofit; | |
} | |
/** | |
* HTTP response body converted to specified {@code type}. {@code null} if there is no | |
* response. | |
* | |
* @throws IOException if unable to convert the body to the specified {@code type}. | |
*/ | |
public <T> T getErrorBodyAs(Class<T> type) throws IOException { | |
if (response == null || response.errorBody() == null) { | |
return null; | |
} | |
Converter<ResponseBody, T> converter = retrofit.responseConverter(type, new Annotation[0]); | |
return converter.convert(response.errorBody()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment