Como devo lidar com "Sem conexão à Internet" com o Retrofit no Android

119

Eu gostaria de lidar com situações em que não há conexão com a internet. Normalmente eu corria:

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

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

( daqui ) antes de enviar as solicitações para a rede e notificar o usuário se não houver conexão com a Internet.

Pelo que vi, o Retrofit não lida especificamente com essa situação. Se não houver conexão com a Internet, RetrofitErroracabarei com o tempo limite como motivo.

Se eu gostaria de incorporar esse tipo de verificação em cada solicitação HTTP com o Retrofit, como devo fazê-lo? Ou devo fazê-lo?

obrigado

Alex

AlexV
fonte
Você pode usar o bloco try-catch para capturar a exceção de tempo limite para a conexão http. Em seguida, informe aos usuários sobre o status da conexão com a Internet. Não é bonito, mas uma solução alternativa.
Tugrul
8
Sim, mas é muito mais rápido verificar com o Android se ele possui conexão à Internet, em vez de esperar para obter o tempo limite da conexão do soquete.
AlexV
A Consulta Android lida com "sem Internet" e retorna o código de erro correspondente em breve. Talvez valha a pena substituí-lo por aQuery? Outra solução é criar um ouvinte sobre alterações na rede e, assim, o aplicativo saberá sobre a disponibilidade da Internet antes de enviar a solicitação.
Stan
para o retrofit 2, consulte github.com/square/retrofit/issues/1260
ghanbari

Respostas:

62

O que acabei fazendo foi criar um cliente Retrofit personalizado que verifica a conectividade antes de executar uma solicitação e gera uma exceção.

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

e depois use-o ao configurar RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
fonte
1
De onde é a sua classe NetworkConnectivityManager? Personalizadas?
NPike 3/04
Sim, personalizado. É basicamente o código da questão
AlexV
2
OkHttpClient não funciona em vez disso, use OKClient (). Resposta agradável do BDW. obrigado.
Harshvardhan Trivedi
Obrigado :-). Editou sua resposta com o uso correto do OkHttp.
user1007522
1
@MominAlAziz dependendo de como você definir NoConnectivityException, você pode torná-lo estender IOExceptionou torná-lo estenderRuntimeException
AlexV
45

Desde a atualização, 1.8.0isso foi preterido

retrofitError.isNetworkError()

você tem que usar

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

}

existem vários tipos de erros que você pode manipular:

NETWORK Ocorreu uma IOException ao se comunicar com o servidor, por exemplo, Tempo limite, Sem conexão, etc ...

CONVERSION Uma exceção foi lançada ao (des) serializar um corpo.

HTTP Um código de status HTTP não-200 foi recebido do servidor, por exemplo, 502, 503, etc.

UNEXPECTEDOcorreu um erro interno ao tentar executar uma solicitação. É uma boa prática relançar essa exceção para que seu aplicativo falhe.

Muhammad Alfaifi
fonte
8
Evite usar equalsvariáveis ​​em excesso, sempre use CONSTANT.equals(variable)para evitar a possível NullPointerException. Ou ainda melhor, neste caso, enums aceitar == comparação assim error.getKind() == RetrofitError.Kind.NETWORKpode ser uma abordagem melhor
MariusBudin
1
Substitua Java pelo Kotlin se você está cansado de NPEs e outras limitações de sintaxe / dor
Louis CAD
42

Com o Retrofit 2, usamos uma implementação do OkHttp Interceptor para verificar a conectividade da rede antes de enviar a solicitação. Se não houver rede, lance uma exceção conforme apropriado.

Isso permite que você lide especificamente com problemas de conectividade de rede antes de acessar o 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
fonte
3
É falha se você ativar o modo Avião e depois desativá-lo, porque você está configurando o var apenas uma vez, tornando o observável inútil.
Oliver Dixon
3
Então você está fazendo o OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))que deve estar aqui?
Rumid
3
Você pode incluir esse código para que as pessoas tenham uma imagem completa?
Oliver Dixon
1
@ Kevin, como posso garantir que ele será atualizado assim que a conexão estiver disponível?
Rumid 8/17
3
essa deve ser a resposta aceita. mais um exemplo do que aqui: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV, você tem certeza de que o RetrofitError contém um tempo limite como motivo (SocketTimeOutException quando getCause () é chamado) quando não há conexão com a Internet?

Até onde eu sei, quando não há conexão com a Internet, o RetrofitError contém uma ConnectionException como causa.

Se você implementar um ErrorHandler, poderá fazer algo assim:

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
fonte
1
Existe um ErrorHandler fornecido na fonte de Retrofit que você pode usar. Se você não manipular o erro, o Retrofit fornecerá um RetrofitError de [java.net.UnknownHostException: Não foi possível resolver o host "example.com": nenhum endereço associado ao nome do host]
codificado
1
@ Codeversed, então, isNetworkErrorse livrar de incapaz de resolver o erro do host?
Onipresente 15/05
2
como você implementaria isso em sua interface, cliente? Quero dizer, a que você conecta essa classe?
FRR 25/05
23
cause.isNetworkError () está obsoleto : useerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Para Retrofit 1

Quando você recebe um Throwableerro da sua solicitação http, pode detectar se é um erro de rede com um 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
fonte
5

basta fazer isso, você será notificado mesmo sobre questões como

UnknownHostException

,

SocketTimeoutException

e outros.

 @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
fonte
1

você pode usar esse 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;
    }
}

Implementação em seus 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
fonte