139

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

AlexV
  • 3,836
  • 7
  • 31
  • 37
  • You may use try-catch block to catch timeout exception for http connection. Then, tell users about internet connection status. Not pretty but an alternative solution. – Tugrul Dec 26 '13 at 14:48
  • 10
    Yes, but it's much faster to check with Android if it has internet connection instead of waiting to get connection timeout from socket – AlexV Dec 26 '13 at 15:41
  • Android Query handles the "no internet" and returns corresponding error code soon enough. Maybe it worth to replace it with aQuery? Another solution is to create a listener on network changes and thus app will know about internet availability before sending the request. – Stan Dec 26 '13 at 18:17
  • for retrofit 2, see https://github.com/square/retrofit/issues/1260 – ghanbari May 03 '16 at 14:11

8 Answers8

67

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(), ...))
AlexV
  • 3,836
  • 7
  • 31
  • 37
47

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.

Muhammad Alfaifi
  • 5,662
  • 2
  • 19
  • 23
  • 11
    Avoid using `equals` over variables, always use `CONSTANT.equals(variable)` instead to avoid possible NullPointerException. Or even better in this case, enums accept == comparison so `error.getKind() == RetrofitError.Kind.NETWORK` might be a better approach – MariusBudin May 12 '17 at 10:34
  • 1
    Replace Java with Kotlin if you're tired of NPEs and other syntax limitations/pain – Louis CAD Jun 25 '18 at 13:38
47

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";
    }
}
William Reed
  • 1,717
  • 1
  • 17
  • 30
Kevin
  • 1,829
  • 1
  • 21
  • 22
  • What does this mean: isNetworkActive.subscribe( _isNetworkActive -> this.isNetworkActive = _isNetworkActive, _error -> Analytics.error("NetworkActive error " + _error.getMessage())); – Name is Nilay Nov 25 '16 at 10:06
  • This method just checks the network connection or does it also check internet connectivity ? – Name is Nilay Nov 25 '16 at 11:27
  • Could you add the `imports` needed? – Zapnologica Jan 08 '17 at 19:44
  • 3
    It's flawed if you turn on Airplane mode then turn it off because you are only setting the var once making the observable useless. – Oliver Dixon Feb 24 '17 at 18:47
  • 6
    So you are making a `OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))` What should be in HERE? – Rumid Apr 28 '17 at 14:45
  • @OliverDixon look at the code more closely. Each time our observable observes a change in connectivity (implementation not included here), it updates the local variable so that, when a request comes into the interceptor, it has the latest value of connectivity. – Kevin Apr 28 '17 at 19:52
  • 5
    Please can you include that code so people get a whole picture? – Oliver Dixon May 02 '17 at 08:01
  • @OliverDixon that code is included in the original question. – Kevin May 02 '17 at 21:33
  • @Kevin I don't get it either. I even created separate [question](http://stackoverflow.com/questions/43684111/how-to-provide-context-with-dagger2) to understand what you meant. – Rumid May 04 '17 at 14:38
  • 1
    @Kevin how can I make sure it will update once connection will be available? – Rumid May 08 '17 at 09:31
  • Please show how do you create connectivity observable – Leo DroidCoder Jan 26 '18 at 10:42
  • The question was how to use *Retrofit* to handle no internet connection. Given an observable that emits true/false as internet availability changes, this answer is complete solution. If you want to know "how can I detect the internet status on Android", go search that question. If you want to know "how does RxJava work", go look that up too. – Kevin Feb 14 '18 at 16:36
  • 3
    this should be the accepted answer. more example of it here: http://www.migapro.com/detect-offline-error-in-retrofit-2/ – j2emanue Jun 28 '18 at 16:48
  • 1
    @Rumid HERE --> replace with -> Observable.just(NetworkUtil.hasNetwork()), where NetworkUtil.hasNetwork() is my method for checking Network connection and is return true/false – Krste Moskov Sep 03 '18 at 23:41
35

@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 ...]  
        }
    }

}
saguinav
  • 497
  • 4
  • 5
  • 1
    There is a provided ErrorHandler in the Retrofit source you can use. If you do not handle the error yourself, Retrofit will give you a RetrofitError of [ java.net.UnknownHostException: Unable to resolve host "example.com": No address associated with hostname ] – Codeversed May 14 '14 at 14:03
  • 1
    @Codeversed so does having `isNetworkError` get rid of unable to resolve host error? – Omnipresent May 15 '14 at 01:36
  • 2
    how would you implement this into your interface, client? I mean, what do you connect this class to? – frankelot May 25 '14 at 19:40
  • 23
    cause.isNetworkError() is **deprecated** : use `error.getKind() == RetrofitError.Kind.NETWORK` – Hugo Gresse Feb 19 '15 at 13:21
15

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)
  }
}
KBog
  • 3,800
  • 1
  • 25
  • 31
8

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
} }
Deepak sharma
  • 687
  • 7
  • 8
6

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!";
        }
    }
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147
2

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);        
    }
David Hackro
  • 3,652
  • 6
  • 41
  • 61