¿Cómo puedo manejar el cuerpo de respuesta vacío con Retrofit 2?

125

Recientemente comencé a usar Retrofit 2 y me enfrenté a un problema al analizar el cuerpo de respuesta vacío. Tengo un servidor que responde solo con código http sin ningún contenido dentro del cuerpo de respuesta.

¿Cómo puedo manejar solo la metainformación sobre la respuesta del servidor (encabezados, código de estado, etc.)?

Yevgen Derkach
fuente

Respuestas:

216

Editar:

Como señala Jake Wharton,

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

es la mejor manera de ir en comparación con mi respuesta original:

Puede devolver a ResponseBody, lo que omitirá el análisis de la respuesta.

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

Entonces en tu llamada,

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
fuente
58
Aún mejor: uso Voidque no solo tiene una mejor semántica sino que es (ligeramente) más eficiente en el caso vacío y mucho más eficiente en un caso no vacío (cuando simplemente no te importa el cuerpo).
Jake Wharton
1
@JakeWharton Ese es un gran comportamiento. Gracias por señalarlo. Respuesta actualizada
iagreen
2
Gran respuesta. Una razón para no usar Void es si tiene un recurso que solo devuelve un cuerpo cuando la solicitud no tiene éxito y desea convertir el errorBody ResponseBody en algún tipo específico o común.
77
@JakeWharton Gran sugerencia para usar Void. ¿ UnitUsar el código Kotlin daría el mismo beneficio que Voiden Java para Retrofit?
Akshay Chordiya
66
@ akshay-chordiya Acabo de comprobar, Uniten Kotlin NO funciona, Voidsin embargo. Supongo que hay un cheque codificado en alguna parte.
user3363866
40

Si usa RxJava, entonces es mejor usarlo Completableen este caso

Representa un cálculo diferido sin ningún valor, pero solo una indicación de finalización o excepción. La clase sigue un patrón de eventos similar al de Reactive-Streams: onSubscribe (onError | onComplete)?

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

en la respuesta aceptada:

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

Si el punto final devuelve el código de respuesta de falla, todavía estará en el onNexty tendrá que verificar el código de respuesta usted mismo.

Sin embargo, si lo usas Completable.

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

solo tendrás onCompletey onError. si el código de respuesta es exitoso, se disparará; de lo onCompletecontrario, se disparará onError.

Ahmad El-Melegy
fuente
1
¿Qué contendrá el onError Throwableargumento, en ese caso? Encuentro este limpiador, pero a menudo todavía tenemos que mirar el código de respuesta y el cuerpo para detectar fallas.
big_m
24

Si está utilizando rxjava, use algo como:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);
Geng Jiawen
fuente
Eso es lo que estaba encontrando! ¡Gracias!
Sirelon
He usado ResposeBody con RxJava2 y Retrofit2 para la solicitud PUT REST. Funcionó bien
Moti Bartov
1
Tenemos una API de punto final que devuelve el cuerpo vacío cuando tiene éxito y el cuerpo json cuando se produce un error. Si uso Response <Void>, ¿cómo puedo manejar el caso de error?
KeNVin Favo
¿Qué clase de respuesta usas aquí? ¿Reequipamiento u OKHttps?
Matthias
1
No es una buena opción si maneja errores en excepciones ... no obtiene ninguna excepción con este enfoque, sino un JSON como respuesta al error
Ovi Trif
0

Así es como lo usé con Rx2 y Retrofit2, con la solicitud PUT REST: Mi solicitud tenía un cuerpo json pero solo un código de respuesta http con un cuerpo vacío.

El cliente 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;

}

La interfaz:

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

Luego para usarlo:

    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
fuente