Skip to content

Instantly share code, notes, and snippets.

@imminent
Last active June 13, 2023 02:50
Show Gist options
  • Save imminent/ac7f2a222e22f4009af4 to your computer and use it in GitHub Desktop.
Save imminent/ac7f2a222e22f4009af4 to your computer and use it in GitHub Desktop.
Call retrying with Retrofit 2.0
package com.example.api;
import java.util.Map;
import retrofit.Call;
import retrofit.http.Body;
import retrofit.http.GET;
import retrofit.http.POST;
public interface Api {
@Retry(1)
@GET("action")
Call getAction();
@Retry
@POST("action")
Call postAction(@Body Map<String, Object> requestBody);
}
package com.example.api;
import com.squareup.okhttp.OkHttpClient;
import retrofit.GsonConverterFactory;
import retrofit.Retrofit;
public class ApiClient {
public final Api api;
public ApiClient(OkHttpClient client) {
final Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RetryCallAdapterFactory.create())
.baseUrl("https://api.example.com")
.build();
api = retrofit.create(Api.class);
}
}
package com.example.api;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Makes the Call retry on failure
*/
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Retry {
int value() default 3;
}
package com.example.api;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import retrofit.Call;
import retrofit.CallAdapter;
import retrofit.Callback;
import retrofit.Response;
import retrofit.Retrofit;
/**
* Retries calls marked with {@link Retry}.
*/
public class RetryCallAdapterFactory implements CallAdapter.Factory {
private final ScheduledExecutorService mExecutor;
private RetryCallAdapterFactory() {
mExecutor = Executors.newScheduledThreadPool(1);
}
public static RetryCallAdapterFactory create() {
return new RetryCallAdapterFactory();
}
@Override
public CallAdapter<?> get(final Type returnType, Annotation[] annotations, Retrofit retrofit) {
boolean hasRetryAnnotation = false;
int value = 0;
for (Annotation annotation : annotations) {
if (annotation instanceof Retry) {
hasRetryAnnotation = true;
value = ((Retry) annotation).value();
}
}
final boolean shouldRetryCall = hasRetryAnnotation;
final int maxRetries = value;
final CallAdapter<?> delegate = retrofit.nextCallAdapter(this, returnType, annotations);
return new CallAdapter<Object>() {
@Override
public Type responseType() {
return delegate.responseType();
}
@Override
public <R> Object adapt(Call<R> call) {
return delegate.adapt(shouldRetryCall ? new RetryingCall<>(call, mExecutor, maxRetries) : call);
}
};
}
static final class RetryingCall<T> implements Call<T> {
private final Call<T> mDelegate;
private final ScheduledExecutorService mExecutor;
private final int mMaxRetries;
public RetryingCall(Call<T> delegate, ScheduledExecutorService executor, int maxRetries) {
mDelegate = delegate;
mExecutor = executor;
mMaxRetries = maxRetries;
}
@Override
public Response<T> execute() throws IOException {
return mDelegate.execute();
}
@Override
public void enqueue(Callback<T> callback) {
mDelegate.enqueue(new RetryingCallback<>(mDelegate, callback, mExecutor, mMaxRetries));
}
@Override
public void cancel() {
mDelegate.cancel();
}
@SuppressWarnings("CloneDoesntCallSuperClone" /* Performing deep clone */)
@Override
public Call<T> clone() {
return new RetryingCall<>(mDelegate.clone(), mExecutor, mMaxRetries);
}
}
// Exponential backoff approach from https://developers.google.com/drive/web/handle-errors
static final class RetryingCallback<T> implements Callback<T> {
private static Random random = new Random();
private final int mMaxRetries;
private final Call<T> mCall;
private final Callback<T> mDelegate;
private final ScheduledExecutorService mExecutor;
private final int mRetries;
RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries) {
this(call, delegate, executor, maxRetries, 0);
}
RetryingCallback(Call<T> call, Callback<T> delegate, ScheduledExecutorService executor, int maxRetries, int retries) {
mCall = call;
mDelegate = delegate;
mExecutor = executor;
mMaxRetries = maxRetries;
mRetries = retries;
}
@Override
public void onResponse(Response<T> response, Retrofit retrofit) {
mDelegate.onResponse(response, retrofit);
}
@Override
public void onFailure(Throwable throwable) {
// Retry failed request
if (mRetries < mMaxRetries) {
retryCall();
} else {
mDelegate.onFailure(new TimeoutError(throwable));
}
}
private void retryCall() {
mExecutor.schedule(new Runnable() {
@Override
public void run() {
final Call<T> call = mCall.clone();
call.enqueue(new RetryingCallback<>(call, mDelegate, mExecutor, mMaxRetries, mRetries + 1));
}
}, (1 << mRetries) * 1000 + random.nextInt(1001), TimeUnit.MILLISECONDS);
}
}
}
package com.example.api;
import java.io.IOException;
public class TimeoutError extends IOException {
private static final long serialVersionUID = -6469766654369165864L;
public TimeoutError() {
super();
}
public TimeoutError(Throwable cause) {
super(cause);
}
}
@kccheung
Copy link

kccheung commented Dec 7, 2016

Thanks for creating such a useful Retrofit retry helper!

@saeedansari
Copy link

Thanks for the helper! What if we do sync calls with execute(), how retry should be handled?

@developer100rbh
Copy link

The above code is feasible With RETROFIT 1.x.
How it can be reused with RETROFIT 2. Since I am getting compile error while implementing this with Retrofit2. Please help. Thanks!

@dtodt
Copy link

dtodt commented Feb 4, 2019

@developer100rbh, I'm not sure if you already solved, but I was needed to make something with retry and Retrofit 2, so there it is:

https://gist.github.com/dtodt/2b62a18e87375682167027bb7feb6752

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment