Tag

Network Error Handling

Browsing

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.

Welcome guys, You are looking for a centralized network solution in Android than you are in right place. In this android app tutorials, We’ll learn centralized network error handling Retrofit in Android. For doing that we’ll create a sample application that contains implementation Retrofit, RxJava with centralized network handling. Let’s see, how to do that.

Introduction

I give you a little bit idea about the approach, that I want to be implemented. You guys know well Retrofit, most popular networking library in Android. It very convenient and highly customizable based on the requirement and use cases.

Mostly we used Retrofit for Rest APIs call for showing the content from remote server. Every network call requires network connectivity to be accomplished. Most of times developers need to show proper error messages on the screen when a network goes down. Some professional apps show data from cache when it’s running out of internet or slow in connection. Basically, we make decisions based on use-case that we have to show data from cache or need to a proper screen. I will cover both use case in this tutorial.

Final Outcome

1. Add dependencies in app build.gradle file

Open the app level build.gradle file and add Rxjava and Retrofit dependencies

  // Retrofit instance
  implementation 'com.squareup.retrofit2:retrofit:2.5.0'
  implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

  // RxJava2 Dependencies
  implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
  implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

2. Do Retrofit configuration with OkHttp

Go to the network folder in source and create a new file named is CompRoot.

package com.example.internetconnection.di;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.example.internetconnection.DemoApp;
import com.example.internetconnection.network.ApiService;
import com.example.internetconnection.network.ConnectionInterceptor;
import com.example.internetconnection.network.ConnectionListener;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class CompRoot {
  private Retrofit retrofit;
  private static final String BASE_URL = "https://reqres.in/api/";

  public ApiService getService() {
    if (retrofit == null) {
      retrofit = new Retrofit
          .Builder()
          .baseUrl(BASE_URL)
          .client(provideOkHttpClient())
          .addConverterFactory(GsonConverterFactory.create())
          .build();
    }

    return retrofit.create(ApiService.class);
  }

  private OkHttpClient provideOkHttpClient() {
    OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    httpClient.connectTimeout(30, TimeUnit.SECONDS);
    httpClient.readTimeout(30, TimeUnit.SECONDS);

    httpClient.addInterceptor(new ConnectionInterceptor() {
      @Override
      public boolean isInternetAvailable() {
        return CompRoot.this.isNetworkAvailable(DemoApp.getContext());
      }

      @Override
      public void onInternetUnavailable() {
        ConnectionListener.getInstance().notifyNetworkChange(false);
      }
    });
    return httpClient.build();
  }

  private boolean isNetworkAvailable(Context context) {
    ConnectivityManager cm =
        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
    return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
  }
}

CompRoot is just a helper class. These things we can do with Dagger2. But In this tutorial, we are not using Dagger2 for decreasing the complexity of logic.

The code snippets we have put two major logic.
  • I’m creating RxBus named is ConnectionListener.
  • Create a subclass of ConnectionInterceptor

2.1. Create a singleton named is ConnectionListener

ConnectionListener is just a RxBus that written in Singletone design pattern. You see here we publishing notifyNetworkChange() and listen Observable in listenNetworkChange().

package com.example.internetconnection.network;

import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;

public class ConnectionListener {
  private static ConnectionListener mInstance;

  public static ConnectionListener getInstance() {
    if (mInstance == null) {
      mInstance = new ConnectionListener();
    }
    return mInstance;
  }

  private ConnectionListener() {
  }

  //this how to create our bus
  private BehaviorSubject<Boolean> publisher = BehaviorSubject.create();

  public void notifyNetworkChange(Boolean isConnected) {
    publisher.onNext(isConnected);
  }

  // Listen should return an Observable
  public Observable<Boolean> listenNetworkChange() {
    return publisher;
  }
}

2.2 Write an abstract class which is a subclass of ConnectionInterceptor

This ConnectionInterceptor is intercepted every APIs call and check that time internet connection is available or not. based on that call appropriate methods

package com.example.internetconnection.network;

import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public abstract class ConnectionInterceptor implements Interceptor {

  public abstract boolean isInternetAvailable();

  public abstract void onInternetUnavailable();

  @Override
  public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    if (!isInternetAvailable()) {
      onInternetUnavailable();
    }
    return chain.proceed(request);
  }
}

3. Write API interface method for Retrofit

This is the basic setup of APIs calling in Retrofit Android

package com.example.internetconnection.network;

import com.example.internetconnection.network.model.User;
import retrofit2.Call;
import retrofit2.http.GET;

public interface ApiService {

  @GET("users")
  Call<User> getUsers();
}

4. Build a superclass BaseActivity

Go to UI package and create a class named is BaseActivity which extends AppCompatActivity. Following things, we are doing in this class.

  • We initializing CompRoot and exposing methods name is getCompRoot()
  • On Activity onCreate() methods instantiate CompositeDisposable and depose on onDestroy()
  • In Activity onCreate() we are adding InternetConnectionListener
  • expose onInternetUnavailable() methods and call it on subscribe()
package com.example.internetconnection.ui;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.internetconnection.R;
import com.example.internetconnection.di.CompRoot;
import com.example.internetconnection.network.ConnectionListener;
import com.google.android.material.snackbar.Snackbar;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

@SuppressLint("Registered")
public class BaseActivity extends AppCompatActivity {
  private CompRoot compRoot;
  private CompositeDisposable disposable;

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    compRoot = new CompRoot();
    disposable = new CompositeDisposable();
    addInternetConnectionListener();
  }

  CompRoot getCompRoot() {
    return compRoot;
  }

  private void addInternetConnectionListener() {
    disposable.add(ConnectionListener.getInstance()
        .listenNetworkChange().subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<Boolean>() {
          @Override public void accept(Boolean aBoolean) throws Exception {
            onInternetUnavailable();
          }
        }));
  }

  @Override protected void onDestroy() {
    super.onDestroy();
    disposable.dispose();
  }

  protected void onInternetUnavailable() {
    showSnackBar(getString(R.string.no_internet_connection));
  }

  protected void showSnackBar(String message) {
    Snackbar.make(getView(), message, Snackbar.LENGTH_SHORT).show();
  }

  private View getView() {
    return findViewById(android.R.id.content);
  }
}

5. Open MainActivity and Paste below code

In this activity, we are simply calling APIs. and override onInternetUnavailable() for updating UI

package com.example.internetconnection.ui;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.example.internetconnection.R;
import com.example.internetconnection.network.ApiService;
import com.example.internetconnection.network.model.User;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends BaseActivity {

  private static final String TAG = "MainActivity";

  ApiService apiService;
  TextView textView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // Bind Views
    textView = findViewById(R.id.textView);

    // Get ApiService service instance
    apiService = getCompRoot().getService();
    executeApiCall();
  }

  public void onRetry(View view) {
    executeApiCall();
  }

  private void executeApiCall() {
    apiService.getUsers().enqueue(new Callback<User>() {
      @Override public void onResponse(Call<User> call, Response<User> response) {
        Log.d(TAG, "onResponse: " + response.body());
      }

      @Override public void onFailure(Call<User> call, Throwable t) {
        Log.d(TAG, "onFailure: ");
      }
    });
  }

  @Override protected void onInternetUnavailable() {
    super.onInternetUnavailable();
    textView.setText(getString(R.string.no_internet_connection));
  }
}

Conclusion

That’s it! In this android tutorial, we have learned centralized network error handling Retrofit in Android. In previously we have to check internet connection before each APIs calls. Using this approach, you’ll able to manage all network call without writing any boilerplate code.

Get Solution Code

Retrofit globally error handling with RxJava