implementar - ¿Cómo debo manejar "Sin conexión a Internet" con Retrofit en Android
retrofit post (6)
Me gustaría manejar situaciones cuando no hay conexión a Internet. Por lo general, corría:
ConnectivityManager cm =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
activeNetwork.isConnectedOrConnecting();
(desde here ) antes de enviar las solicitudes a la red y notificar al usuario si no hay 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 timeout como razón.
Si quisiera incorporar este tipo de control en cada solicitud HTTP con Retrofit, ¿cómo debería hacerlo? O debería hacerlo en absoluto.
Gracias
Alex
@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, 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 ...]
}
}
}
Con Retrofit 2, utilizamos una implementación OkHttp Interceptor para verificar la conectividad de red antes de enviar la solicitud. Si no hay red, lanza una excepción según corresponda.
Esto le permite a uno manejar específicamente problemas de conectividad de red antes de presionar Retrofit.
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Response;
import rx.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";
}
}
Cuando obtiene un error Throwable
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!";
}
}
}
ACTUALIZACIÓN : Debo decir que esto solo funciona con Retrofit 1, no Retrofit 2.
Desde la actualización 1.8.0
esto 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 al comunicarse con el servidor, por ejemplo, Timeout, Sin conexión, etc.
CONVERSION
Se produjo una excepción al (de) serializar un cuerpo.
HTTP
Se recibió un código de estado HTTP no 200 del servidor, por ejemplo, 502, 503, etc.
UNEXPECTED
Se produjo un error interno al intentar ejecutar una solicitud. Lo mejor es volver a lanzar esta excepción para que su aplicación falle.
Lo que terminé haciendo es crear un cliente de 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 RestAdapter
al configurar RestAdapter
RestAdapter.Builder().setEndpoint(serverHost)
.setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
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 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);
}