web-dev-qa-db-fra.com

Réessayer la demande en utilisant Retrofit 2

Comment ajouter une fonctionnalité de nouvelle tentative aux demandes envoyées par Retrofit 2 library. Quelque chose comme:

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

        @Override
        public void onFailure(Throwable t) {
            ...
        }
    }).retryOnFailure(5 /* times */);
21
Ashkan Sarlak

J'ai finalement fait quelque chose comme ça, pour ceux qui sont intéressés:

1

J'ai d'abord créé une classe abstraite 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);
    }
}

En utilisant cette classe, je peux faire quelque chose comme ceci:

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

2

Ce n'est pas complètement satisfaisant car je dois passer la même serviceCall deux fois. Cela peut être déroutant car on peut penser que la seconde serviceCall (qui entre dans le constructeur de CallbackWithRetry) devrait ou pourrait être quelque chose de différent du premier (que nous appelons la méthode enqueue sur elle)

J'ai donc implémenté une classe d'assistance 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);
            }
        });
    }

}

Et je peux l'utiliser comme ça:

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

Avec cela, je dois passer une méthode standard Callback à enqueueWithRetry et cela me permet d'implémenter onFailure (bien que dans la méthode précédente, je puisse l'implémenter aussi)

Voilà comment j'ai résolu le problème. Toute suggestion pour un meilleur design serait appréciée. 

49
Ashkan Sarlak

J'ai créé une implémentation personnalisée de l'interface de rappel, vous pouvez très bien l'utiliser à la place du rappel d'origine. Si l'appel aboutit, la méthode onResponse () est appelée. Si après un nouvel essai pour un nombre défini de répétitions, l'appel échoue, onFailedAfterRetry () est appelé.

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

7
milechainsaw

Allez avec RxJava Observable et appelez retry () Doc: https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators

3
guillaume_fr

J'ai fait quelque chose d'assez similaire à Ashkan Sarlak, mais depuis que Retrofit 2.1 passe le Call<T> dans la méthode onFailure, vous pouvez simplifier une classe abstraite CallbackWithRetry<T>. Voir:

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

ashkan-sarlak réponds que je travaille bien et que je veux juste le mettre à jour.

À partir de modernisation 2.1

onFailure(Throwable t) 

Changer en

onFailure(Call<T> call, Throwable t)

Donc, cela le rend si facile maintenant. Créez simplement CallbackWithRetry.Java comme ceci

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

C'est tout! vous pouvez simplement l'utiliser comme ça

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

Je pense que pour Android, nous n’avons pas besoin de demander une adaptation ultérieure. Nous pouvons utiliser Workmanager (qui prédéfinit l’API Android) . Nous pouvons utiliser "ListenableWorker.Result.SUCCESS", "ListenableWorker.Result.RETRY", etc. et atteindre les objectifs ci-dessus.

0
SIVAKUMAR.J

Avec rattrapage 2.5

Maintenant, il est possible de faire des appels de synchronisation asynchrone via Java.util.concurrent.CompletableFuture, le code attend son achèvement, ce qui est très agréable.

Voici un Gist avec une solution de travail.

0
DTodt