Reputation: 7564
So, I am working with an API that is clearly defined and is designed to not return a payload body on DELETE
and PUT
operations.
This was acceptable in Rx 0.X and Rx 1.x. Now I'm updating to Rx 2 and having an existential crisis with how I should handle the null values. The content-length and body are of course null causing:
java.lang.NullPointerException: Null is not a valid element
at io.reactivex.internal.queue.SpscLinkedArrayQueue.offer(SpscLinkedArrayQueue.java:68)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.onNext(ObservableObserveOn.java:116)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:63)
in the doOnNext.
I've seen many people suggest Optional<>
but I need to support Java7 as well for use case reasons. I attempted back-porting but I couldn't quite get it to work. I also don't want to bloat and import the Guava library for their version.
I also noticed flatMap may also help me handle this opposed to map and I'm reading up on the differences.
Currently I have a very crude, OkHttp3 Interceptor that will check the status, check if the payload is empty, and add dummy content which just feels so wrong.
I've also tried to add a convert factory.
Can anyone offer suggestions and guide me on what the proper path is? Sure, the API can change, but 204 isn't supposed to have a payload by virtue of it's definition as an HTTP status code.
compile('com.squareup.retrofit2:retrofit:2.1.0') {
exclude module: 'okhttp'
}
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
compile 'io.reactivex.rxjava2:rxjava:2.0.5'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.0.1'
Upvotes: 16
Views: 7497
Reputation: 5097
Depending on the use-case below could be another solution.
It could be handled with Okhttp3 Interceptor.
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Response
import okhttp3.ResponseBody
object EmptyBodyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.isSuccessful.not() || response.code().let { it != 204 && it != 205 }) {
return response
}
if ((response.body()?.contentLength() ?: -1) >= 0) {
return response.newBuilder().code(200).build()
}
val emptyBody = ResponseBody.create(MediaType.get("text/plain"), "")
return response
.newBuilder()
.code(200)
.body(emptyBody)
.build()
}
}
Upvotes: 0
Reputation: 1433
For somebody who's still searching for the answer: replacing Void to some empty class solved my problem. Like this:
class EmptyResponse {}
and just use this class instead of Void
Upvotes: 0
Reputation: 2390
Since you have a RxJava2 and retrofit 2, you could use a more readable way of describing what it is you really want from an endpoint using a Completable
.
Completable
by design returns only onComplete()
and onError(Exception)
so you know what is going on right the moment you see it(you only care about execution not the return value) and not wonder what will be under Response<Void>
.
So your endpoint call should look like:
@DELETE(...)
Completable deleteFile(...args);
More types are supported, see the Retrofit 2 RxJava 2 Adapter page.Single
is one that stands out in terms of api calls.
Upvotes: 15
Reputation: 50578
You need to declare your request method in Retrofit
like:
@DELETE(...)
Call<Void> deleteFile(...args);
In RxJava your Observable
has to be typed:
@DELETE(...)
Observable<Response<Void>> deleteFile(...args);
In onNext()
or doOnNext()
you will receive the Response
normally, if the request is successful.
Having Void
will not send the response body to the converter for further deserialization. All empty-response Call
s should be typed as Void
.
Upvotes: 30