-
-
Save msangel/6d1e65c84df273d9b3fbd04b55add72d to your computer and use it in GitHub Desktop.
Retrofit 1 error handling behaviour in Retrofit 2.3.0
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 extends CallAdapter.Factory { | |
private final Executor callbackExecutor; | |
ErrorHandlingExecutorCallAdapterFactory(Executor callbackExecutor) { | |
this.callbackExecutor = callbackExecutor; | |
} | |
@Override | |
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, final Retrofit retrofit) { | |
if (getRawType(returnType) != Call.class) { | |
return null; | |
} | |
final Type responseType = getCallResponseType(returnType); | |
return new CallAdapter<Object, Call<?>>() { | |
@Override public Type responseType() { | |
return responseType; | |
} | |
@Override public Call<Object> adapt(Call<Object> call) { | |
return new ExecutorCallbackCall<>(callbackExecutor, call, retrofit); | |
} | |
}; | |
} | |
static final class ExecutorCallbackCall<T> implements Call<T> { | |
final Executor callbackExecutor; | |
final Call<T> delegate; | |
private final Retrofit retrofit; | |
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate, Retrofit retrofit) { | |
this.callbackExecutor = callbackExecutor; | |
this.delegate = delegate; | |
this.retrofit = retrofit; | |
} | |
@Override | |
public void enqueue(final Callback<T> callback) { | |
checkNotNull(callback, "callback == null"); | |
delegate.enqueue(new MyExecutorCallback<>(callbackExecutor, delegate, callback, this, retrofit)); | |
} | |
@Override public boolean isExecuted() { | |
return delegate.isExecuted(); | |
} | |
@Override public Response<T> execute() throws IOException { | |
return delegate.execute(); | |
} | |
@Override public void cancel() { | |
delegate.cancel(); | |
} | |
@Override public boolean isCanceled() { | |
return delegate.isCanceled(); | |
} | |
@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone. | |
@Override public Call<T> clone() { | |
return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone(), retrofit); | |
} | |
@Override public Request request() { | |
return delegate.request(); | |
} | |
} | |
static class MyExecutorCallback<T> implements Callback<T>{ | |
final Executor callbackExecutor; | |
final Call<T> delegate; | |
final Callback<T> callback; | |
final ExecutorCallbackCall<T> executorCallbackCall; | |
private Retrofit retrofit; | |
public MyExecutorCallback(Executor callbackExecutor, Call<T> delegate, Callback<T> callback, ExecutorCallbackCall<T> executorCallbackCall, Retrofit retrofit){ | |
this.callbackExecutor = callbackExecutor; | |
this.delegate = delegate; | |
this.callback = callback; | |
this.executorCallbackCall = executorCallbackCall; | |
this.retrofit = retrofit; | |
} | |
@Override | |
public void onResponse(final Call<T> call, final Response<T> response) { | |
if(response.isSuccessful()){ | |
callbackExecutor.execute(new Runnable() { | |
@Override | |
public void run() { | |
callback.onResponse(executorCallbackCall, response); | |
} | |
}); | |
} else { | |
callbackExecutor.execute(new Runnable() { | |
@Override | |
public void run() { | |
callback.onFailure(executorCallbackCall, RetrofitException.httpError(response.raw().request().url().toString(), response, retrofit)); | |
} | |
}); | |
} | |
} | |
@Override | |
public void onFailure(Call<T> call, 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() { | |
callback.onFailure(executorCallbackCall, 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>"); | |
} | |
return getParameterUpperBound(0, (ParameterizedType) returnType); | |
} | |
static <T> T checkNotNull(@Nullable T object, String message) { | |
if (object == null) { | |
throw new NullPointerException(message); | |
} | |
return object; | |
} | |
} |
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 | |
import java.io.IOException; | |
import java.lang.annotation.Annotation; | |
import okhttp3.ResponseBody; | |
import retrofit2.Converter; | |
import retrofit2.Response; | |
import retrofit2.Retrofit; | |
// 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.responseBodyConverter(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