How should I handle "No internet connection" with Retrofit on Android

AndroidRetrofit

Android Problem Overview


I'd like to handle situations when there is no internet connection. Usually I'd run:

ConnectivityManager cm =
    (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                  activeNetwork.isConnectedOrConnecting();

(from here) before sending the requests to the network and notify user if there were no internet connection.

From what I saw Retrofit does not handle this situation specifically. If there is no internet connection I'll just get RetrofitError with timeout as a reason.

If I'd like to incorporate this kind of check into every HTTP request with Retrofit, how should I do it? Or should I do it at all.

Thanks

Alex

Android Solutions


Solution 1 - Android

What I ended up doing is creating a custom Retrofit client that checks for connectivity before executing a request and throws an exception.

public class ConnectivityAwareUrlClient implements Client {

    Logger log = LoggerFactory.getLogger(ConnectivityAwareUrlClient.class);

    public ConnectivityAwareUrlClient(Client wrappedClient, NetworkConnectivityManager ncm) {
        this.wrappedClient = wrappedClient;
        this.ncm = ncm;
    }

    Client wrappedClient;
    private NetworkConnectivityManager ncm;

    @Override
    public Response execute(Request request) throws IOException {
        if (!ncm.isConnected()) {
            log.debug("No connectivity %s ", request);
            throw new NoConnectivityException("No connectivity");
        }
        return wrappedClient.execute(request);
    }
}

and then use it when configuring RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))

Solution 2 - Android

Since retrofit 1.8.0 this has been deprecated

retrofitError.isNetworkError()

you have to use

if (retrofitError.getKind() == RetrofitError.Kind.NETWORK)
{
    
}

there are multiple types of errors you can handle:

NETWORK An IOException occurred while communicating to the server, e.g. Timeout, No connection, etc...

CONVERSION An exception was thrown while (de)serializing a body.

HTTP A non-200 HTTP status code was received from the server e.g. 502, 503, etc...

UNEXPECTED An internal error occurred while attempting to execute a request. It is best practice to re-throw this exception so your application crashes.

Solution 3 - Android

With Retrofit 2, we use an OkHttp Interceptor implementation to check for network connectivity ahead of sending the request. If no network, throw an exception as appropriate.

This allows one to specifically handle network connectivity issues before hitting Retrofit.

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Response;
import io.reactivex.Observable

public class ConnectivityInterceptor implements Interceptor {

    private boolean isNetworkActive;

    public ConnectivityInterceptor(Observable<Boolean> isNetworkActive) {
       isNetworkActive.subscribe(
               _isNetworkActive -> this.isNetworkActive = _isNetworkActive,
               _error -> Log.e("NetworkActive error " + _error.getMessage()));
    }

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        if (!isNetworkActive) {
            throw new NoConnectivityException();
        }
        else {
            Response response = chain.proceed(chain.request());
            return response;
        }
    }
}

public class NoConnectivityException extends IOException {

    @Override
    public String getMessage() {
        return "No network available, please check your WiFi or Data connection";
    }
}

Solution 4 - Android

@AlexV are you sure that the RetrofitError contains a timeout as a reason (SocketTimeOutException when getCause() is called) when there is no internet connection?

As far as I know when there is no internet connection the RetrofitError contains a ConnectionException as cause.

If you implement an ErrorHandler you can do something like this:

public class RetrofitErrorHandler implements ErrorHandler {

    @Override
    public Throwable handleError(RetrofitError cause) {
        if (cause.isNetworkError()) {
            if (cause.getCause() instanceof SocketTimeoutException) {
                return new MyConnectionTimeoutException();
            } else {
                return new MyNoConnectionException();
            }
        } else {
            [... do whatever you want if it's not a network error ...]  
        }
    }

}

Solution 5 - Android

Here's what I did on API 29 & API 30:

1. I created a simple WiFiService class that will hold the connectivityManager:

   class WifiService {
    private lateinit var wifiManager: WifiManager
    private lateinit var connectivityManager: ConnectivityManager

    companion object {
        val instance = WifiService()
    }

    fun initializeWithApplicationContext (context: Context) {
        wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
        connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    }

    // Helper that detects if online
    fun isOnline(): Boolean {
        val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
        if (capabilities != null) {
            when {
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> return true
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> return true
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> return true
            }
        }
        return false
      }
   }

2. Create the ConnectivityInterceptor to check for internet access:

   class ConnectivityInterceptor: Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        if (!WifiService.instance.isOnline()) {
            throw IOException("No internet connection")
        } else {
            return chain.proceed(chain.request())
        }
     }
   }

3. Use it in Retrofit2 as follows:

  class RestApi {
    private val okHttpClient by lazy {
        OkHttpClient.Builder()
            .addInterceptor(ConnectivityInterceptor())
            .build()
    }

    // Define all the retrofit clients
    private val restApiClient by lazy {
        Retrofit.Builder()
            .baseUrl("http://localhost:10000")
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
     }

     // ...
  }

4. Finally initialize the WifiService as such:

class MainApplication: Application() {
  companion object {
    lateinit var instance:  MainApplication
  }

  override fun onCreate() {
    super.onCreate()
    instance = this

    setupServices()
  }

  private fun setupServices() {
    WifiService.instance.initializeWithApplicationContext(this)
  }
}

Solution 6 - Android

just do this you will notified even for issues like

> UnknownHostException

,

> SocketTimeoutException

and others.

 @Override public void onFailure(Call<List<BrokenGitHubRepo>> call, Throwable t) {  
if (t instanceof IOException) {
    Toast.makeText(ErrorHandlingActivity.this, "this is an actual network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
    // logging probably not necessary
}
else {
    Toast.makeText(ErrorHandlingActivity.this, "conversion issue! big problems :(", Toast.LENGTH_SHORT).show();
    // todo log to some central bug tracking service
} }

Solution 7 - Android

For Retrofit 1

When you get a Throwable error from your http request, you can detect whether it is a network error with a method like this:

String getErrorMessage(Throwable e) {
    RetrofitError retrofitError;
    if (e instanceof RetrofitError) {
        retrofitError = ((RetrofitError) e);
        if (retrofitError.getKind() == RetrofitError.Kind.NETWORK) {
            return "Network is down!";
        }
    }
}

Solution 8 - Android

you can use this code

Response.java

import com.google.gson.annotations.SerializedName;

/**
 * Created by hackro on 19/01/17.
 */

public class Response {
    @SerializedName("status")
    public String status;

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @SuppressWarnings({"unused", "used by Retrofit"})
    public Response() {
    }

    public Response(String status) {
        this.status = status;
    }
}

NetworkError.java

import android.text.TextUtils;

import com.google.gson.Gson;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import retrofit2.adapter.rxjava.HttpException;

import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

/**
 * Created by hackro on 19/01/17.
 */

public class NetworkError extends Throwable {
    public static final String DEFAULT_ERROR_MESSAGE = "Please try again.";
    public static final String NETWORK_ERROR_MESSAGE = "No Internet Connection!";
    private static final String ERROR_MESSAGE_HEADER = "Error Message";
    private final Throwable error;

    public NetworkError(Throwable e) {
        super(e);
        this.error = e;
    }

    public String getMessage() {
        return error.getMessage();
    }

    public boolean isAuthFailure() {
        return error instanceof HttpException &&
                ((HttpException) error).code() == HTTP_UNAUTHORIZED;
    }

    public boolean isResponseNull() {
        return error instanceof HttpException && ((HttpException) error).response() == null;
    }

    public String getAppErrorMessage() {
        if (this.error instanceof IOException) return NETWORK_ERROR_MESSAGE;
        if (!(this.error instanceof HttpException)) return DEFAULT_ERROR_MESSAGE;
        retrofit2.Response<?> response = ((HttpException) this.error).response();
        if (response != null) {
            String status = getJsonStringFromResponse(response);
            if (!TextUtils.isEmpty(status)) return status;

            Map<String, List<String>> headers = response.headers().toMultimap();
            if (headers.containsKey(ERROR_MESSAGE_HEADER))
                return headers.get(ERROR_MESSAGE_HEADER).get(0);
        }

        return DEFAULT_ERROR_MESSAGE;
    }

    protected String getJsonStringFromResponse(final retrofit2.Response<?> response) {
        try {
            String jsonString = response.errorBody().string();
            Response errorResponse = new Gson().fromJson(jsonString, Response.class);
            return errorResponse.status;
        } catch (Exception e) {
            return null;
        }
    }

    public Throwable getError() {
        return error;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        NetworkError that = (NetworkError) o;

        return error != null ? error.equals(that.error) : that.error == null;

    }

    @Override
    public int hashCode() {
        return error != null ? error.hashCode() : 0;
    }
}

Implementation in your methods

        @Override
        public void onCompleted() {
            super.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            super.onError(e);
            networkError.setError(e);
            Log.e("Error:",networkError.getAppErrorMessage());
        }

        @Override
        public void onNext(Object obj) {   super.onNext(obj);        
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexVView Question on Stackoverflow
Solution 1 - AndroidAlexVView Answer on Stackoverflow
Solution 2 - AndroidMuhammad AlfaifiView Answer on Stackoverflow
Solution 3 - AndroidKevinView Answer on Stackoverflow
Solution 4 - AndroidsaguinavView Answer on Stackoverflow
Solution 5 - AndroidKBogView Answer on Stackoverflow
Solution 6 - AndroidDeepak sharmaView Answer on Stackoverflow
Solution 7 - AndroidIgorGanapolskyView Answer on Stackoverflow
Solution 8 - AndroidDavid HackroView Answer on Stackoverflow