Getting java.util.NoSuchElementException when trying to do POST to server. The answer from server is 204/success, but exception is still raised

Viewed 1554

I doing POST to my server. The result is 204 / Success code, the body for the result is empty. java.util.NoSuchElementException is raised right after the result from server. My guess is that is because the respond contains no body (Void), but I am very unsure

My interface

    @POST("register/")
    fun register(@Body phone: PhoneBody): Single<Void>

PhoneBody data class PhoneBody(val phone: String)

My query

  disposable += registrationRepository.register(phone)
            .addSchedulers()
            .doOnSubscribe { viewState.showLoading() }
            .doAfterTerminate { viewState.hideLoading() }
            .subscribe({it ->
                Log.d("PAPDEB", "$it Personal Acc Presenter")

            }, this::handleError)

it never goes to in-subscribe lambda, it goes right into this::handle which is

        if (t is ApiException) {
            viewState.showError(t.message)
        } else if (t is IOException) {
            viewState.showError("No internet connection")
        }

        if (BuildConfig.DEBUG) {
            t.printStackTrace()
        }
    }
D/OkHttp: --> POST https://.../register/
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 24
D/OkHttp: {"phone":"+79090000000"}
D/OkHttp: --> END POST (24-byte body)
D/OkHttp: <-- 204 https://.../register/ (386ms)
D/OkHttp: server: nginx/1.13.12
D/OkHttp: date: Tue, 25 Jun 2019 20:52:49 GMT
D/OkHttp: vary: Accept, Accept-Language, Cookie, Origin
D/OkHttp: allow: POST, OPTIONS
D/OkHttp: x-frame-options: SAMEORIGIN
D/OkHttp: content-language: ru
D/OkHttp: strict-transport-security: max-age=31536000
D/OkHttp: <-- END HTTP

W/System.err: java.util.NoSuchElementException
W/System.err:     at io.reactivex.internal.operators.observable.ObservableSingleSingle$SingleElementObserver.onComplete(ObservableSingleSingle.java:111)
W/System.err:     at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onComplete(BodyObservable.java:66)
W/System.err:     at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:48)
W/System.err:     at io.reactivex.Observable.subscribe(Observable.java:12246)
W/System.err:     at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
W/System.err:     at io.reactivex.Observable.subscribe(Observable.java:12246)
W/System.err:     at io.reactivex.internal.operators.observable.ObservableSingleSingle.subscribeActual(ObservableSingleSingle.java:35)
W/System.err:     at io.reactivex.Single.subscribe(Single.java:3575)
W/System.err:     at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
W/System.err:     at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/System.err:     at java.lang.Thread.run(Thread.java:764)

I would be very thankful to hear any ideas of what is wrong and how to get to lambda

2 Answers

As @CommonsWare said in the comment to the question the problem was that I used Single (from logic of ctrl+c ctrl+V -> change return model) I changed it to Completable and now it works!

Refer to https://github.com/square/retrofit/issues/2867

Using Completable is one of the way out but if the response will sometime carry data you wanted to keep it using Single<T> you can add the Interceptor to your okhttpclient. All credits goes to jgavazzisp and LouisCAD.

Interceptor

    class 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("application/json"), "{}")

        return response
                .newBuilder()
                .code(200)
                .body(emptyBody)
                .build()
    }
}

use on OkHttpClient

private val okHttpClient: OkHttpClient by lazy { OkHttpClient().newBuilder()
        .addInterceptor(EmptyBodyInterceptor()).build() }

private var retrofit : Retrofit = Retrofit.Builder().client(okHttpClient).build()
Related