Bagaimana saya harus menangani "Tidak ada koneksi internet" dengan Retrofit di Android

119

Saya ingin menangani situasi ketika tidak ada koneksi internet. Biasanya saya akan lari:

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

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

(dari sini ) sebelum mengirim permintaan ke jaringan dan memberi tahu pengguna jika tidak ada koneksi internet.

Dari apa yang saya lihat Retrofit tidak menangani situasi ini secara khusus. Jika tidak ada koneksi internet, saya hanya akan mendapatkan RetrofitErrorwaktu tunggu sebagai alasan.

Jika saya ingin memasukkan pemeriksaan semacam ini ke dalam setiap permintaan HTTP dengan Retrofit, bagaimana saya harus melakukannya? Atau haruskah saya melakukannya sama sekali.

Terima kasih

Alex

AlexV
sumber
Anda dapat menggunakan blok coba-tangkap untuk menangkap pengecualian waktu tunggu untuk koneksi http. Kemudian, beri tahu pengguna tentang status koneksi internet. Tidak cantik tapi solusi alternatif.
Tugrul
8
Ya, tetapi jauh lebih cepat untuk memeriksa dengan Android apakah ia memiliki koneksi internet daripada menunggu untuk mendapatkan batas waktu koneksi dari soket
AlexV
Android Query menangani "no internet" dan segera mengembalikan kode error yang sesuai. Mungkin layak untuk menggantinya dengan aQuery? Solusi lain adalah membuat pendengar pada perubahan jaringan dan dengan demikian aplikasi akan tahu tentang ketersediaan internet sebelum mengirim permintaan.
Stan
untuk retrofit 2, lihat github.com/square/retrofit/issues/1260
ghanbari

Jawaban:

63

Apa yang akhirnya saya lakukan adalah membuat klien Retrofit khusus yang memeriksa konektivitas sebelum menjalankan permintaan dan membuat pengecualian.

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

lalu gunakan saat mengkonfigurasi RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))
AlexV
sumber
1
Dari mana kelas NetworkConnectivityManager Anda? Adat?
NPike
Ya, adat. Ini pada dasarnya adalah kode dari pertanyaan
AlexV
2
OkHttpClient tidak bekerja sebagai gantinya gunakan OKClient (). Jawaban bagus BDW. Terima kasih.
Harshvardhan Trivedi
Terima kasih :-). Mengedit jawaban Anda dengan penggunaan OkHttp yang benar.
pengguna1007522
1
@MominAlAziz tergantung pada bagaimana Anda mendefinisikan NoConnectivityException, Anda dapat memperpanjang IOExceptionatau memperpanjangRuntimeException
AlexV
45

Sejak retrofit, 1.8.0ini sudah tidak digunakan lagi

retrofitError.isNetworkError()

Anda harus menggunakan

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

}

ada beberapa jenis kesalahan yang dapat Anda tangani:

NETWORK IOException terjadi saat berkomunikasi ke server, misalnya Timeout, Tidak ada koneksi, dll ...

CONVERSION Pengecualian diberikan saat (de) membuat serialisasi tubuh.

HTTP Kode status HTTP non-200 diterima dari server misalnya 502, 503, dll ...

UNEXPECTEDTerjadi kesalahan internal saat mencoba menjalankan permintaan. Praktik terbaiknya adalah membuang kembali pengecualian ini sehingga aplikasi Anda mogok.

Muhammad Alfaifi
sumber
8
Hindari penggunaan equalsvariabel berlebih, selalu gunakan CONSTANT.equals(variable)sebagai gantinya untuk menghindari kemungkinan NullPointerException. Atau bahkan lebih baik dalam hal ini, enum menerima == perbandingan jadi error.getKind() == RetrofitError.Kind.NETWORKmungkin pendekatan yang lebih baik
MariusBudin
1
Ganti Java dengan Kotlin jika Anda bosan dengan NPE dan batasan / gangguan sintaks lainnya
Louis CAD
42

Dengan Retrofit 2, kami menggunakan implementasi OkHttp Interceptor untuk memeriksa konektivitas jaringan sebelum mengirim permintaan. Jika tidak ada jaringan, berikan pengecualian yang sesuai.

Ini memungkinkan seseorang untuk secara khusus menangani masalah konektivitas jaringan sebelum mencapai 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
sumber
3
Cacat jika Anda mengaktifkan mode Pesawat kemudian mematikannya karena Anda hanya menyetel var sekali membuat observasi tidak berguna.
Oliver Dixon
3
Jadi Anda membuat OkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))Apa yang seharusnya ada di SINI?
Rumid
3
Tolong bisakah Anda memasukkan kode itu sehingga orang-orang mendapatkan gambaran utuh?
Oliver Dixon
1
@Kevin bagaimana cara memastikannya akan diperbarui setelah koneksi tersedia?
Rumid
3
ini harus menjadi jawaban yang diterima. lebih banyak contoh di sini: migapro.com/detect-offline-error-in-retrofit-2
j2emanue
35

@AlexV Anda yakin bahwa RetrofitError berisi batas waktu sebagai alasan (SocketTimeOutException saat getCause () dipanggil) saat tidak ada sambungan internet?

Sejauh yang saya tahu ketika tidak ada koneksi internet, RetrofitError berisi ConnectionException sebagai penyebabnya.

Jika Anda menerapkan ErrorHandler, Anda dapat melakukan sesuatu seperti ini:

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.dll
sumber
1
Ada ErrorHandler yang disediakan di sumber Retrofit yang dapat Anda gunakan. Jika Anda tidak menangani kesalahan itu sendiri, Retrofit akan memberi Anda RetrofitError dari [java.net.UnknownHostException: Tidak dapat menyelesaikan host "example.com": Tidak ada alamat yang terkait dengan nama host]
Dikode
1
@ Codeversed jadi apakah harus isNetworkErrormenyingkirkan tidak dapat menyelesaikan kesalahan host?
Hadir di mana-mana
2
bagaimana Anda menerapkan ini ke antarmuka Anda, klien? Maksud saya, apa yang Anda hubungkan dengan kelas ini?
FRR
23
cause.isNetworkError () tidak digunakan lagi : gunakanerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse
6

Untuk Retrofit 1

Ketika Anda mendapatkan Throwablekesalahan dari permintaan http Anda, Anda dapat mendeteksi apakah itu kesalahan jaringan dengan metode seperti ini:

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

cukup lakukan ini, Anda akan diberi tahu bahkan untuk masalah seperti

UnknownHostException

,

SocketTimeoutException

dan lain-lain.

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

Anda dapat menggunakan kode ini

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

Implementasi dalam metode Anda

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