Jak poradzić sobie z pustą treścią odpowiedzi za pomocą Retrofit 2?

125

Niedawno zacząłem używać Retrofit 2 i napotkałem problem z analizowaniem pustej treści odpowiedzi. Mam serwer, który odpowiada tylko kodem http bez żadnej treści w treści odpowiedzi.

Jak mogę obsłużyć tylko metainformacje dotyczące odpowiedzi serwera (nagłówki, kod statusu itp.)?

Yevgen Derkach
źródło

Odpowiedzi:

216

Edytować:

Jak podkreśla Jake Wharton,

@GET("/path/to/get")
Call<Void> getMyData(/* your args here */);

to najlepszy sposób w porównaniu z moją pierwotną odpowiedzią -

Możesz po prostu zwrócić a ResponseBody, co spowoduje ominięcie analizy odpowiedzi.

@GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);

Wtedy w twoim wezwaniu

Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response) {
        // use response.code, response.headers, etc.
    }

    @Override
    public void onFailure(Throwable t) {
        // handle failure
    }
});
iagreen
źródło
58
Jeszcze lepiej: użyj, Voidktóre nie tylko ma lepszą semantykę, ale jest (nieco) bardziej wydajne w pustym przypadku i znacznie bardziej wydajne w niepustym przypadku (gdy po prostu nie dbasz o treść).
Jake Wharton
1
@JakeWharton To świetne zachowanie. Dzięki za zwrócenie uwagi. Zaktualizowano odpowiedź.
iagreen
2
Świetna odpowiedź. Jednym z powodów, dla których nie należy używać Void, jest sytuacja, w której masz zasób, który zwraca treść tylko wtedy, gdy żądanie nie powiedzie się i chcesz przekonwertować errorBody ResponseBody na określony lub wspólny typ.
7
@JakeWharton Świetna propozycja użycia Void. Czy użycie Unitw kodzie Kotlin dałoby te same korzyści, co Voidw Javie dla Retrofit?
Akshay Chordiya
6
@ akshay-chordiya Właśnie sprawdziłem, Unitw Kotlinie NIE działa, Voidjednak działa. Zakładam, że gdzieś jest zakodowany czek.
user3363866
40

Jeśli używasz RxJava, lepiej jest użyć Completablew tym przypadku

Reprezentuje odroczone obliczenia bez żadnej wartości, ale tylko wskazanie do ukończenia lub wyjątku. Klasa ma podobny wzorzec zdarzenia jak Reactive-Streams: onSubscribe (onError | onComplete)?

http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html

w zaakceptowanej odpowiedzi:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

Jeśli punkt końcowy zwróci kod odpowiedzi błędu, będzie on nadal znajdował się w onNexti będziesz musiał samodzielnie sprawdzić kod odpowiedzi.

Jeśli jednak używasz Completable.

@GET("/path/to/get")
Completable getMyData(/* your args here */);

będziesz miał tylko onCompletei onError. jeśli kod odpowiedzi się powiedzie, zostanie uruchomiony, a onCompleteinny zostanie uruchomiony onError.

Ahmad El-Melegy
źródło
1
Co onError Throwablew takim razie będzie zawierał argument? Uważam, że jest to czystsze, ale często nadal musimy patrzeć na kod odpowiedzi i treść pod kątem błędów.
big_m
24

Jeśli używasz rxjava, użyj czegoś takiego:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
Geng Jiawen
źródło
To właśnie znalazłem! Dzięki!
Sirelon
Użyłem ResposeBody z RxJava2 i Retrofit2 do żądania PUT REST.
Działało
1
Mamy interfejs API punktu końcowego, który zwraca pustą treść w przypadku sukcesu i treść json w przypadku błędu. Jeśli używasz Response <Void>, jak mogę sobie poradzić z przypadkiem błędu?
KeNVin Favo
Której klasy Response używasz tutaj? Retrofit czy OKHttps?
Matthias
1
Nie jest to dobra opcja, jeśli nie na wyjątkach obsługi błędu .. nie dostaniesz wyjątek z tym podejściem, ale raczej jako JSON odpowiedzi w przypadku błędu
Ovi Trif
0

Oto jak użyłem go z Rx2 i Retrofit2, z żądaniem PUT REST: Moje żądanie miało treść json, ale tylko kod odpowiedzi http z pustą treścią.

Klient API:

public class ApiClient {
public static final String TAG = ApiClient.class.getSimpleName();


private DevicesEndpoint apiEndpointInterface;

public DevicesEndpoint getApiService() {


    Gson gson = new GsonBuilder()
            .setLenient()
            .create();


    OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    okHttpClientBuilder.addInterceptor(logging);

    OkHttpClient okHttpClient = okHttpClientBuilder.build();

    apiEndpointInterface = new Retrofit.Builder()
            .baseUrl(ApiContract.DEVICES_REST_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(DevicesEndpoint.class);

    return apiEndpointInterface;

}

Interfejs:

public interface DevicesEndpoint {
 @Headers("Content-Type: application/json")
 @PUT(ApiContract.DEVICES_ENDPOINT)
 Observable<ResponseBody> sendDeviceDetails(@Body Device device);
}

Następnie, aby go użyć:

    private void sendDeviceId(Device device){

    ApiClient client = new ApiClient();
    DevicesEndpoint apiService = client.getApiService();
    Observable<ResponseBody> call = apiService.sendDeviceDetails(device);

    Log.i(TAG, "sendDeviceId: about to send device ID");
    call.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
        @Override
        public void onSubscribe(Disposable disposable) {
        }

        @Override
        public void onNext(ResponseBody body) {
            Log.i(TAG, "onNext");
        }

        @Override
        public void onError(Throwable t) {
            Log.e(TAG, "onError: ", t);

        }

        @Override
        public void onComplete() {
            Log.i(TAG, "onCompleted: sent device ID done");
        }
    });

}
Moti Bartov
źródło