32

How can I add retry functionality to the requests sent by Retrofit 2 library. Something like:

service.listItems().enqueue(new Callback<List<Item>>() {
        @Override
        public void onResponse(Response<List<Item>> response) {
            ...
        }

        @Override
        public void onFailure(Throwable t) {
            ...
        }
    }).retryOnFailure(5 /* times */);
Ashkan Sarlak
  • 7,124
  • 6
  • 39
  • 51

8 Answers8

65

I finally did something like this, for anyone interested:

1

First I made an abstract class CallbackWithRetry

public abstract class CallbackWithRetry<T> implements Callback<T> {

    private static final int TOTAL_RETRIES = 3;
    private static final String TAG = CallbackWithRetry.class.getSimpleName();
    private final Call<T> call;
    private int retryCount = 0;

    public CallbackWithRetry(Call<T> call) {
        this.call = call;
    }

    @Override
    public void onFailure(Throwable t) {
        Log.e(TAG, t.getLocalizedMessage());
        if (retryCount++ < TOTAL_RETRIES) {
            Log.v(TAG, "Retrying... (" + retryCount + " out of " + TOTAL_RETRIES + ")");
            retry();
        }
    }

    private void retry() {
        call.clone().enqueue(this);
    }
}

Using this class I can do something like this:

serviceCall.enqueue(new CallbackWithRetry<List<Album>>(serviceCall) {
    @Override
    public void onResponse(Response<List<Album>> response) {
        ...
    }
});

2

This is not completely satisfactory because I have to pass same serviceCall twice. This can confusing as one can think the second serviceCall (that goes into constructor of CallbackWithRetry) should or could be something different from first one (which we invoke enqueue method on it)

So I implemented a helper class CallUtils:

public class CallUtils {

    public static <T> void enqueueWithRetry(Call<T> call, final Callback<T> callback) {
        call.enqueue(new CallbackWithRetry<T>(call) {
            @Override
            public void onResponse(Response<T> response) {
                callback.onResponse(response);
            }

            @Override
            public void onFailure(Throwable t) {
                super.onFailure(t);
                callback.onFailure(t);
            }
        });
    }

}

And I can use it like this:

CallUtils.enqueueWithRetry(serviceCall, new Callback<List<Album>>() {
    @Override
    public void onResponse(Response<List<Album>> response) {
        ...
    }

    @Override
    public void onFailure(Throwable t) {
        // Let the underlying method do the job of retrying.
    }
});

With this I have to pass a standard Callback to enqueueWithRetry method and it makes me implement onFailure (Though in the previous method I can implement it too)

So this is how I've solved the issue. Any suggestion for a better design would be appreciated.

Ashkan Sarlak
  • 7,124
  • 6
  • 39
  • 51
  • 1
    On CallUtils the overrided method should be: @Override public void onResponse(Response response, Retrofit retrofit) { callback.onResponse(response, retrofit); } – Ninja Coding Nov 27 '15 at 15:25
  • how we can change the baseUrl from the CallbackWithRetry class? – Ninja Coding Nov 27 '15 at 15:45
  • 6
    Great answer! By the way, with Retrofit 2.1, the `Callback.onFailure(Call call, Throwable t)` already has the "call", don't need `CallUtils` any more. – situee Dec 26 '16 at 10:08
14

I've made custom implementation of the Callback interface, you can pretty much use it in place of original callback. If call is successful, the onResponse() method is called. If after retrying for set amount of repetitions call fails, onFailedAfterRetry() is called.

public abstract class BackoffCallback<T> implements Callback<T> {
private static final int RETRY_COUNT = 3;
/**
 * Base retry delay for exponential backoff, in Milliseconds
 */
private static final double RETRY_DELAY = 300;
private int retryCount = 0;

@Override
public void onFailure(final Call<T> call, Throwable t) {
    retryCount++;
    if (retryCount <= RETRY_COUNT) {
        int expDelay = (int) (RETRY_DELAY * Math.pow(2, Math.max(0, retryCount - 1)));
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                retry(call);
            }
        }, expDelay);
    } else {
        onFailedAfterRetry(t);
    }
}

private void retry(Call<T> call) {
    call.clone().enqueue(this);
}

public abstract void onFailedAfterRetry(Throwable t);

}

https://gist.github.com/milechainsaw/811c1b583706da60417ed10d35d2808f

milechainsaw
  • 274
  • 3
  • 8
  • Implementing this solution within my custom CallAdapter.Factory causes a RuntimeException (Can't create handler inside thread that has not called Looper.prepare()) – Granjero Jun 25 '21 at 19:08
5

ashkan-sarlak answer work great and i'm just try to make it up to date.

From retrofit 2.1

onFailure(Throwable t) 

Change to

onFailure(Call<T> call, Throwable t)

So this make it so easy now.just create CallbackWithRetry.java like this

public abstract class CallbackWithRetry<T> implements Callback<T> {

    private static final int TOTAL_RETRIES = 3;
    private static final String TAG = CallbackWithRetry.class.getSimpleName();
    private int retryCount = 0;

    @Override
    public void onFailure(Call<T> call, Throwable t) {
        Log.e(TAG, t.getLocalizedMessage());
        if (retryCount++ < TOTAL_RETRIES) {
            Log.v(TAG, "Retrying... (" + retryCount + " out of " + TOTAL_RETRIES + ")");
            retry(call);
        }
    }

    private void retry(Call<T> call) {
        call.clone().enqueue(this);
    }
}

That's all! you can simply use it like this

call.enqueue(new CallbackWithRetry<someResponseClass>() {

        @Override
        public void onResponse(@NonNull Call<someResponseClass> call, @NonNull retrofit2.Response<someResponseClass> response) {
            //do what you want
        }
        @Override
        public void onFailure(@NonNull Call<someResponseClass> call, @NonNull Throwable t) {
            super.onFailure(call,t);
            //do some thing to show ui you trying
            //or don't show! its optional
        }
    });
Radesh
  • 13,084
  • 4
  • 51
  • 64
3

Go with RxJava Observable and call retry() Doc: https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators

guillaume_fr
  • 491
  • 3
  • 12
3

I did something quite similar to Ashkan Sarlak, but since Retrofit 2.1 passes the Call<T> into the onFailure method, you can simplify to one CallbackWithRetry<T> abstract class. See:

public abstract class CallbackWithRetry<T> implements Callback<T> {



 private static final String TAG = "CallbackWithRetry";

  private int retryCount = 0;

  private final Logger logger;
  private final String requestName;
  private final int retryAttempts;

  protected CallbackWithRetry(@NonNull Logger logger, @NonNull String requestName, int retryAttempts) {
    this.logger = logger;
    this.requestName = requestName;
    this.retryAttempts = retryAttempts;
  }

  @Override
  public void onFailure(Call<T> call, Throwable t) {
    if (retryCount < retryAttempts) {
      logger.e(TAG, "Retrying ", requestName, "... (", retryCount, " out of ", retryAttempts, ")");
      retry(call);

      retryCount += 1;
    } else {
      logger.e(TAG, "Failed request ", requestName, " after ", retryAttempts, " attempts");
    }
  }

  private void retry(Call<T> call) {
    call.clone().enqueue(this);
  }
}
huwr
  • 1,720
  • 3
  • 19
  • 34
3

With Retrofit 2.5

Now it's possible to make async sync calls through java.util.concurrent.CompletableFuture, the code waits for it's completion wich is very nice.

Here's a gist with a working solution.

DTodt
  • 380
  • 6
  • 19
1

Another solution for this problem if retry is optional :

public class CustomCallback<T> implements Callback<T> {
    @NonNull
    Callback<T> callback;

    private int retryCount = 0;
    private int maxRetry = 0;

    @EverythingIsNonNull
    public CustomCallback(Callback<T> callback) {
        this.callback = callback;
    }


    public CustomCallback<T> retryOnFailure(int nbRetry) {
        maxRetry = nbRetry;
        return this;
    }

    @EverythingIsNonNull
    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        callback.onResponse(call, response);
    }

    @EverythingIsNonNull
    @Override
    public void onFailure(Call<T> call, Throwable t) {
        if (maxRetry > retryCount) {
            retryCount++;
            call.clone().enqueue(this);
            return;
        }
        callback.onFailure(call, t);
    }
}

This way, you can choose if you want retry or not :

//With retry
 myAPI.makeCall().enqueue(new CustomCallback<>(myCallback).retryOnFailure(3));
//Without
 myAPI.makeCall().enqueue(new CustomCallback<>(myCallback));
A. Ferrand
  • 618
  • 6
  • 19
0

I think for android we no need to go for retrofit for this.We can make use of Workmanager (which predefine android api). We can use "ListenableWorker.Result.SUCCESS","ListenableWorker.Result.RETRY" ,etc and achieve the above goals.

SIVAKUMAR.J
  • 4,258
  • 9
  • 45
  • 80