Comment gérer "Pas de connexion Internet" avec Retrofit sur Android

119

Je voudrais gérer les situations où il n'y a pas de connexion Internet. Habituellement, je courrais:

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

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

(à partir d' ici ) avant d'envoyer les demandes au réseau et avertir l'utilisateur s'il n'y a pas de connexion Internet.

D'après ce que j'ai vu, Retrofit ne gère pas spécifiquement cette situation. S'il n'y a pas de connexion Internet, j'obtiendrai juste un RetrofitErrordélai d'attente comme raison.

Si je souhaite intégrer ce type de vérification dans chaque requête HTTP avec Retrofit, comment dois-je le faire? Ou devrais-je le faire du tout.

Merci

Alex

AlexV
la source
Vous pouvez utiliser le bloc try-catch pour intercepter l'exception de délai d'attente pour la connexion http. Ensuite, informez les utilisateurs de l'état de la connexion Internet. Pas joli mais une solution alternative.
Tugrul
8
Oui, mais il est beaucoup plus rapide de vérifier avec Android s'il dispose d'une connexion Internet au lieu d'attendre le délai d'expiration de la connexion de la prise
AlexV
Android Query gère le "pas d'Internet" et renvoie le code d'erreur correspondant assez rapidement. Peut-être vaut-il la peine de le remplacer par une requête? Une autre solution consiste à créer un auditeur sur les changements de réseau et ainsi l'application connaîtra la disponibilité d'Internet avant d'envoyer la demande.
Stan
pour la modernisation 2, voir github.com/square/retrofit/issues/1260
ghanbari

Réponses:

63

Ce que j'ai fini par faire, c'est créer un client Retrofit personnalisé qui vérifie la connectivité avant d'exécuter une demande et lève une 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);
    }
}

puis utilisez-le lors de la configuration RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
la source
1
D'où vient votre classe NetworkConnectivityManager? Douane?
NPike
Oui, personnalisé. C'est essentiellement le code de la question
AlexV
2
OkHttpClient ne fonctionne pas au lieu de cela, utilisez OKClient (). BDW belle réponse. Merci.
Harshvardhan Trivedi
Merci :-). Modification de votre réponse avec la bonne utilisation d'OkHttp.
user1007522
1
@MominAlAziz selon la façon dont vous définissez NoConnectivityException, vous pouvez soit l'étendre, IOExceptionsoit l'étendreRuntimeException
AlexV
45

Depuis la modernisation, 1.8.0cela est obsolète

retrofitError.isNetworkError()

tu dois utiliser

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

}

il existe plusieurs types d'erreurs que vous pouvez gérer:

NETWORK Une IOException s'est produite lors de la communication avec le serveur, par exemple Timeout, No connection, etc ...

CONVERSION Une exception a été lancée lors de la (dé) sérialisation d'un corps.

HTTP Un code d'état HTTP autre que 200 a été reçu du serveur, par exemple 502, 503, etc ...

UNEXPECTEDUne erreur interne s'est produite lors de la tentative d'exécution d'une demande. Il est recommandé de renvoyer cette exception afin que votre application se bloque.

Muhammad Alfaifi
la source
8
Évitez d'utiliser des equalsvariables excessives, utilisez toujours à la CONSTANT.equals(variable)place pour éviter d'éventuelles NullPointerException. Ou encore mieux dans ce cas, les énumérations acceptent la comparaison ==, ce qui error.getKind() == RetrofitError.Kind.NETWORKpourrait être une meilleure approche
MariusBudin
1
Remplacez Java par Kotlin si vous en avez assez des NPE et autres limitations / douleurs de syntaxe
Louis CAD
42

Avec Retrofit 2, nous utilisons une implémentation OkHttp Interceptor pour vérifier la connectivité réseau avant d'envoyer la demande. S'il n'y a pas de réseau, lancez une exception le cas échéant.

Cela permet de gérer spécifiquement les problèmes de connectivité réseau avant de passer à la modernisation.

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
la source
3
Il est imparfait si vous activez le mode Avion, puis désactivez-le car vous ne définissez le var qu'une fois, ce qui rend l'observable inutile.
Oliver Dixon
3
Alors vous faites un OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))Que devrait être ici?
Rumid
3
Pouvez-vous inclure ce code pour que les gens aient une vue d'ensemble?
Oliver Dixon
1
@Kevin comment puis-je m'assurer qu'il sera mis à jour une fois que la connexion sera disponible?
Rumid
3
cela devrait être la réponse acceptée. plus d'exemple ici: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV êtes-vous sûr que RetrofitError contient un timeout comme raison (SocketTimeOutException lorsque getCause () est appelé) lorsqu'il n'y a pas de connexion Internet?

Autant que je sache, quand il n'y a pas de connexion Internet, RetrofitError contient une ConnectionException comme cause.

Si vous implémentez un ErrorHandler, vous pouvez faire quelque chose comme ceci:

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
la source
1
Il existe un ErrorHandler fourni dans la source Retrofit que vous pouvez utiliser. Si vous ne gérez pas l'erreur vous-même, Retrofit vous donnera une RetrofitError de [java.net.UnknownHostException: Impossible de résoudre l'hôte "example.com": Aucune adresse associée au nom d'hôte]
Code inversé
1
@Codeversed isNetworkErrorse débarrasse-t-il de l'incapacité de résoudre l'erreur de l'hôte?
Omniprésent
2
comment implémenteriez-vous cela dans votre interface, client? Je veux dire, à quoi connectez-vous cette classe?
FRR
23
cause.isNetworkError () est obsolète : utilisationerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Pour la rénovation 1

Lorsque vous obtenez une Throwableerreur de votre requête http, vous pouvez détecter s'il s'agit d'une erreur réseau avec une méthode comme celle-ci:

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

faites simplement cela, vous serez averti même pour des problèmes tels que

UnknownHostException

,

SocketTimeoutException

et d'autres.

 @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
la source
2

vous pouvez utiliser ce 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;
    }
}

Implémentation dans vos méthodes

        @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
la source