Android & Kotlin

Retrying Request with Retrofit Android

Pinterest LinkedIn Tumblr

In this post,  I will show you how to setup auto retrying the request with retrofit Android. For doing that we’ll create an example application that contains auto retrying request functionality with Retrofit. Let’s get started.

Android app your network calls can and will fail randomly due to low bandwidth and low network connectivity. Hence it is a very good idea to add auto-retry policy for some important network calls. It makes for better user experience.

1. Create a new Android Project

Let open the android studio and create a new project with the default template. In this sample app, we are going to use Retrofit and Gson so we have to add dependencies in build.gradle.

  implementation 'com.squareup.retrofit2:retrofit:2.7.0'
  implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
2. Define Retry annotation

Create a @Retry annotation interface. Which APIs annotated with this annotation retry functionally will auto-enable. Here a have set the default attempts is 3 you can change it based on your app need.

package com.retrofitautoretryexample.retrofit;

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;

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Retry {
  int max() default 3;
}
3. Write a retry call adapter factory

Create a new java class named RetryCallAdapterFactory which extends CallAdapter.Factory. In this factory class, we’ll check is request is annotated with @Retry or not. If annotated then every failure request try to call again at least 3 times.

package com.retrofitautoretryexample.retrofit;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;

public class RetryCallAdapterFactory extends CallAdapter.Factory {
  private static final String TAG = "RetryCallAdapterFactory";

  public static RetryCallAdapterFactory create() {
    return new RetryCallAdapterFactory();
  }

  @Nullable
  @Override
  public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations,
      @NonNull Retrofit retrofit) {
    /**
     * You can setup a default max retry count for all connections.
     */
    int itShouldRetry = 0;
    final Retry retry = getRetry(annotations);
    if (retry != null) {
      itShouldRetry = retry.max();
    }
    Log.d(TAG, "Starting a CallAdapter with {} retries." + itShouldRetry);
    return new RetryCallAdapter<>(
        retrofit.nextCallAdapter(this, returnType, annotations),
        itShouldRetry
    );
  }

  private Retry getRetry(@NonNull Annotation[] annotations) {

    for (Annotation annotation : annotations) {
      if (annotation instanceof Retry) {
        return (Retry) annotation;
      }
    }
    return null;
  }

  static final class RetryCallAdapter<R, T> implements CallAdapter<R, T> {

    private final CallAdapter<R, T> delegated;
    private final int maxRetries;

    public RetryCallAdapter(CallAdapter<R, T> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }

    @Override
    public Type responseType() {
      return delegated.responseType();
    }

    @Override
    public T adapt(final Call<R> call) {
      return delegated.adapt(maxRetries > 0 ? new RetryingCall<>(call, maxRetries) : call);
    }
  }

  static final class RetryingCall<R> implements Call<R> {

    private final Call<R> delegated;
    private final int maxRetries;

    public RetryingCall(Call<R> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }

    @Override
    public Response<R> execute() throws IOException {
      return delegated.execute();
    }

    @Override
    public void enqueue(@NonNull Callback<R> callback) {
      delegated.enqueue(new RetryCallback<>(delegated, callback, maxRetries));
    }

    @Override
    public boolean isExecuted() {
      return delegated.isExecuted();
    }

    @Override
    public void cancel() {
      delegated.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegated.isCanceled();
    }

    @Override
    public Call<R> clone() {
      return new RetryingCall<>(delegated.clone(), maxRetries);
    }

    @Override
    public Request request() {
      return delegated.request();
    }
  }

  static final class RetryCallback<T> implements Callback<T> {

    private final Call<T> call;
    private final Callback<T> callback;
    private final int maxRetries;

    public RetryCallback(Call<T> call, Callback<T> callback, int maxRetries) {
      this.call = call;
      this.callback = callback;
      this.maxRetries = maxRetries;
    }

    private final AtomicInteger retryCount = new AtomicInteger(0);

    @Override
    public void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
      if (!response.isSuccessful() && retryCount.incrementAndGet() <= maxRetries) {
        Log.d(TAG, "Call with no success result code: {} " + response.code());
        retryCall();
      } else {
        callback.onResponse(call, response);
      }
    }

    @Override
    public void onFailure(@NonNull Call<T> call, @NonNull Throwable t) {
      Log.d(TAG, "Call failed with message:  " + t.getMessage(), t);
      if (retryCount.incrementAndGet() <= maxRetries) {
        retryCall();
      } else if (maxRetries > 0) {
        Log.d(TAG, "No retries left sending timeout up.");
        callback.onFailure(call,
            new TimeoutException(String.format("No retries left after %s attempts.", maxRetries)));
      } else {
        callback.onFailure(call, t);
      }
    }

    private void retryCall() {
      Log.w(TAG, "" + retryCount.get() + "/" + maxRetries + " " + " Retrying...");
      call.clone().enqueue(this);
    }
  }
}
4. Create POJO for server response

All retry stuff is done. Meanwhile, I will show you how to use this class in your project. So for doing that create a POJO for parsing server response. So, create a new class named is UserResponse and paste below code.

package com.retrofitautoretryexample.model;

import com.google.gson.annotations.SerializedName;

public class UserResponse {
  @SerializedName("authToken")
  private String authToken;
  @SerializedName("data")
  private Object data;
  @SerializedName("error")
  private Boolean error;
  @SerializedName("message")
  private String message;
  @SerializedName("statusCode")
  private Long statusCode;

  public String getAuthToken() {
    return authToken;
  }

  public Object getData() {
    return data;
  }

  public Boolean getError() {
    return error;
  }

  public String getMessage() {
    return message;
  }

  public Long getStatusCode() {
    return statusCode;
  }
}
5. Write a UserApiService interface for Retrofit

For integrating with server let’s create a Retrofit Interface and add below code

package com.retrofitautoretryexample;

import com.retrofitautoretryexample.model.UserResponse;
import com.retrofitautoretryexample.retrofit.Retry;
import retrofit2.Call;
import retrofit2.http.GET;

public interface UserApiService {

  @Retry
  @GET("user")
  Call<UserResponse> getUsers();
}
6. Create a Retrofit Client and add RetryCallAdapterFactory with client

Furthermore, create a retrofit instance that returns UserApiService service class. I have added RetryCallAdapterFactory with retrofit client.

package com.retrofitautoretryexample;

import com.retrofitautoretryexample.retrofit.RetryCallAdapterFactory;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {

  private static final String BASE_URL = "https://androidwave.com/api/";

  private static Retrofit retrofit = null;

  public static UserApiService getApiService() {
    if (retrofit == null) {
      retrofit = new Retrofit
          .Builder()
          .baseUrl(BASE_URL)
          .addCallAdapterFactory(RetryCallAdapterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .build();
    }
    return retrofit.create(UserApiService.class);
  }
}
7. Finally, add below code in MainActivity

In this activity, we’ll call get user API and show the result on TextView

package com.retrofitautoretryexample;

import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.retrofitautoretryexample.model.UserResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

  private TextView txvResult;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txvResult = findViewById(R.id.txvResults);

    UserApiService apiService = RetrofitClient.getApiService();
    apiService.getUsers().enqueue(new Callback<UserResponse>() {
      @Override public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
        txvResult.setText(response.body().getMessage());
        Toast.makeText(getApplicationContext(), "Success " + response.body().getMessage(),
            Toast.LENGTH_LONG).show();
      }

      @Override public void onFailure(Call<UserResponse> call, Throwable t) {
        txvResult.setText(t.getMessage());
        Toast.makeText(getApplicationContext(), "Failure " + t.getMessage(), Toast.LENGTH_LONG)
            .show();
      }
    });
  }
}
Verify this solution

For verifying this solution, run this app and see you will able to see the result on TextView. Now disconnect the internet from the device and run again. If you check on logcat output will be like this

    RetryCallAdapterFactory: Starting a CallAdapter with {} retries.3 
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 1/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 2/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 3/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: No retries left sending timeout up.
Conclusion

With the help of this tutorial, we have learned the implementation Retrying Request with Retrofit. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development.

Write A Comment