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

9 minutos de lectura

avatar de usuario
AlexV

Me gustaría manejar situaciones en las que no hay conexión a Internet. Por lo general, ejecutaría:

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

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

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

Por lo que vi, Retrofit no maneja esta situación específicamente. Si no hay conexión a Internet, solo obtendré RetrofitError con el tiempo de espera como una razón.

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

  • Puede usar el bloque try-catch para detectar 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 bonito, pero es una solución alternativa.

    – Tugrul

    26 de diciembre de 2013 a las 14:48

  • 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 tiempo de conexión del socket.

    – AlexV

    26 de diciembre de 2013 a las 15:41

  • Android Query maneja el “sin internet” y devuelve el código de error correspondiente lo suficientemente pronto. ¿Tal vez valga la pena reemplazarlo con aQuery? Otra solución es crear un detector de cambios en la red y, por lo tanto, la aplicación sabrá sobre la disponibilidad de Internet antes de enviar la solicitud.

    – Stan

    26 de diciembre de 2013 a las 18:17

  • para reequipamiento 2, véase github.com/square/retrofit/issues/1260

    – ghanbari

    3 mayo 2016 a las 14:11

Lo que terminé haciendo fue crear un cliente Retrofit personalizado que verifica la conectividad antes de ejecutar una solicitud y genera 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 usarlo al configurar RestAdapter

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

  • ¿De dónde es su clase NetworkConnectivityManager? ¿Disfraz?

    – NPike

    03/04/2014 a las 17:58

  • Sí, personalizado. Básicamente es el código de la pregunta.

    – AlexV

    7 abr 2014 a las 10:34


  • OkHttpClient no funciona en lugar de usar OKClient (). BDW buena respuesta. gracias.

    – Harshvardhan Trivedi

    3 de diciembre de 2014 a las 11:08

  • Gracias :-). Editó su respuesta con el uso correcto de OkHttp.

    – usuario1007522

    13 de febrero de 2015 a las 9:56

  • @MominAlAziz dependiendo de cómo lo definas NoConnectivityExceptionpuedes hacer que se extienda IOException o hacer que se extienda RuntimeException

    – AlexV

    25 de junio de 2015 a las 8:11

avatar de usuario
Muhammad Alfaifi

Desde la actualización 1.8.0 esto ha sido obsoleto

retrofitError.isNetworkError()

tienes que usar

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

}

Hay varios tipos de errores que puede manejar:

NETWORK Ocurrió una IOException mientras se comunicaba 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 que no es 200 del servidor, por ejemplo, 502, 503, etc.

UNEXPECTED Se 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.

  • Evitar el uso de equals sobre variables, siempre use CONSTANT.equals(variable) en su lugar, para evitar posibles NullPointerException. O incluso mejor en este caso, las enumeraciones aceptan == comparación, por lo que error.getKind() == RetrofitError.Kind.NETWORK podría ser un mejor enfoque

    – MariusBudin

    12 mayo 2017 a las 10:34

  • Reemplace Java con Kotlin si está cansado de NPE y otras limitaciones/dolor de sintaxis

    – Luis CAD

    25 de junio de 2018 a las 13:38

avatar de usuario
Kevin

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, lanza una excepción según corresponda.

Esto permite manejar específicamente los problemas de conectividad de la red antes de presionar 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";
    }
}

  • 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

    24 de febrero de 2017 a las 18:47


  • Así que estás haciendo un OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE)) ¿Qué debería estar AQUÍ?

    – Rúmido

    28 de abril de 2017 a las 14:45

  • ¿Puedes incluir ese código para que la gente tenga una idea completa?

    –Oliver Dixon

    2 de mayo de 2017 a las 8:01

  • @Kevin, ¿cómo puedo asegurarme de que se actualizará una vez que la conexión esté disponible?

    – Rúmido

    8 de mayo de 2017 a las 9:31

  • esta debería ser la respuesta aceptada. más ejemplo de esto aquí: migapro.com/detect-offline-error-in-retrofit-2

    – j2emanue

    28 de junio de 2018 a las 16:48

avatar de usuario
KBog

Esto es lo que hice en API 29 & API 30:

1. Creé una clase WiFiService simple que contendrá el administrador de conectividad:

   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. Cree ConnectivityInterceptor para verificar el acceso a Internet:

   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. Úselo en Retrofit2 de la siguiente manera:

  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. Finalmente, inicialice WifiService como tal:

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

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

    setupServices()
  }

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

  • Hay un ErrorHandler proporcionado en la fuente Retrofit que puede usar. Si no maneja el error usted mismo, Retrofit le dará un RetrofitError de [ java.net.UnknownHostException: Unable to resolve host “example.com”: No address associated with hostname ]

    – Codeversed

    14 mayo 2014 a las 14:03

  • @Codeversed también lo es tener isNetworkError deshacerse de no poder resolver el error del host?

    – omnipresente

    15 de mayo de 2014 a las 1:36

  • ¿Cómo implementaría esto en su interfaz, cliente? Quiero decir, ¿a qué conectas esta clase?

    – Frankelot

    25 mayo 2014 a las 19:40


  • causa.isNetworkError() es obsoleto : usar error.getKind() == RetrofitError.Kind.NETWORK

    – Hugo Gresse

    19 de febrero de 2015 a las 13:21

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

UnknownHostException

,

Excepción de tiempo de espera de socket

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

avatar de usuario
Olcay Ertas

Para reacondicionamiento 1

Cuando obtienes un Throwable error 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!";
        }
    }
}

avatar de usuario
David Hackro

puedes usar este codigo

Respuesta.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 sus 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);        
    }

¿Ha sido útil esta solución?