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.)?

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

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.


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 */);

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

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.