¿Cómo debo manejar "Sin conexión a Internet" con Retrofit en Android?

119

Me gustaría manejar situaciones en las que no hay conexión a Internet. Normalmente corro:

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

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

(desde aquí ) antes de enviar las solicitudes a la red y notificar al usuario si no hubo conexión a Internet.

Por lo que vi, Retrofit no maneja esta situación específicamente. Si no hay conexión a Internet, simplemente obtendré el RetrofitErrortiempo de espera como motivo.

Si quisiera incorporar este tipo de verificación en cada solicitud HTTP con Retrofit, ¿cómo debo hacerlo? O debería hacerlo en absoluto.

Gracias

Alex

AlexV
fuente
Puede usar el bloque try-catch para capturar la excepción de tiempo de espera para la conexión http. Luego, informe a los usuarios sobre el estado de la conexión a Internet. No es bonita, pero es una solución alternativa.
Tugrul
8
Sí, pero es mucho más rápido verificar con Android si tiene conexión a Internet en lugar de esperar a que se
agote el
Android Query maneja "no internet" y devuelve el código de error correspondiente lo suficientemente pronto. ¿Quizás valga la pena reemplazarlo con aQuery? Otra solución es crear un oyente en los cambios de red y, por lo tanto, la aplicación conocerá la disponibilidad de Internet antes de enviar la solicitud.
Stan
para la actualización
ghanbari

Respuestas:

63

Lo que terminé haciendo es crear un cliente Retrofit personalizado que verifica la conectividad antes de ejecutar una solicitud y lanza una excepción.

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

y luego utilícelo al configurar RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
fuente
1
¿De dónde es su clase NetworkConnectivityManager? ¿Personalizado?
NPike
Sí, personalizado. Es básicamente el código de la pregunta
AlexV
2
OkHttpClient no funciona en lugar de usar OKClient (). BDW buena respuesta. gracias.
Harshvardhan Trivedi
Gracias :-). Editó su respuesta con el uso correcto de OkHttp.
user1007522
1
@MominAlAziz dependiendo de cómo lo defina NoConnectivityException, puede extenderlo IOExceptiono extenderloRuntimeException
AlexV
45

Desde la actualización, 1.8.0esto ha quedado obsoleto

retrofitError.isNetworkError()

tienes que usar

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

}

hay varios tipos de errores que puede manejar:

NETWORK Se produjo una IOException durante la comunicación con el servidor, por ejemplo, tiempo de espera, sin conexión, etc.

CONVERSION Se lanzó una excepción al (des) serializar un cuerpo.

HTTP Se recibió un código de estado HTTP distinto de 200 del servidor, por ejemplo, 502, 503, etc.

UNEXPECTEDSe produjo un error interno al intentar ejecutar una solicitud. Es una buena práctica volver a lanzar esta excepción para que su aplicación se bloquee.

Muhammad Alfaifi
fuente
8
Evite usar equalssobre variables, use siempre en su CONSTANT.equals(variable)lugar para evitar posibles NullPointerException. O incluso mejor en este caso, las enumeraciones aceptan == comparación, por lo que error.getKind() == RetrofitError.Kind.NETWORKpodría ser un mejor enfoque
MariusBudin
1
Reemplace Java con Kotlin si está cansado de NPE y otras limitaciones de sintaxis / dolor
Louis CAD
42

Con Retrofit 2, usamos una implementación de OkHttp Interceptor para verificar la conectividad de la red antes de enviar la solicitud. Si no hay red, lance una excepción según corresponda.

Esto le permite a uno manejar específicamente los problemas de conectividad de la red antes de optar por 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";
    }
}
Kevin
fuente
3
Es defectuoso si enciende el modo Avión y luego lo apaga porque solo está configurando el var una vez, lo que hace que el observable sea inútil.
Oliver Dixon
3
Entonces estás haciendo un OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))¿Qué debería estar AQUÍ?
Rumid
3
¿Puede incluir ese código para que la gente tenga una idea completa?
Oliver Dixon
1
@Kevin, ¿cómo puedo asegurarme de que se actualizará una vez que la conexión esté disponible?
Rumid
3
esta debería ser la respuesta aceptada. más ejemplo de esto aquí: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV, ¿está seguro de que RetrofitError contiene un tiempo de espera como motivo (SocketTimeOutException cuando se llama a getCause ()) cuando no hay conexión a Internet?

Hasta donde yo sé, cuando no hay conexión a Internet, el RetrofitError contiene una ConnectionException como causa.

Si implementa un ErrorHandler , puede hacer algo como esto:

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
fuente
1
Hay un ErrorHandler proporcionado en la fuente de Retrofit que puede usar. Si no maneja el error usted mismo, Retrofit le dará un RetrofitError de [java.net.UnknownHostException: No se puede resolver el host "example.com": No hay dirección asociada con el nombre de host]
Codeversed
1
@Codeversed, entonces, ¿ isNetworkErrordeshacerse de no puede resolver el error del host?
Omnipresente
2
¿Cómo implementaría esto en su interfaz, cliente? Quiero decir, ¿a qué conectas esta clase?
FRR
23
cause.isNetworkError () está en desuso : useerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Para reequipamiento 1

Cuando recibe un Throwableerror de su solicitud http, puede detectar si se trata de un error de red con un método como este:

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

solo haga esto, se le notificará incluso para problemas como

UnknownHostException

,

SocketTimeoutException

y otros.

 @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
fuente
2

puedes usar este código

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

Implementación en tus métodos

        @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
fuente