kotlinrx-java2retrywhen

Kotlin, RxJava2: compose and retryWhen after user interaction


during the last two days I read a lot about the rxJava retryWhen operator. Here, here, here and some more I forgot.

But unfortunately I'm not able to get it work. What I'm trying to achive is, that I make an API call. If the call returns an error, I'm showing a SnackBar to the user with a reload button. If the user clicks this button, I would like to resubscribe to the chain.

Here is my code:

interface RetrofitApi {

@GET("/v1/loadMyData")
fun getMyData(): Single<Response<DataResponse>>
}

Where Response is from retrofit2. I need it to wrap the data class to check if response is successful.

The next fun is called in a repository from a ViewModel:

override fun loadMyData(): Observable<Resource<DataResponse>> {
    return retrofitApi
            .getMyData()
            .compose(getRetryTransformer())
            .toObservable()
            .compose(getResponseTransformer())
}

Resource is another wrapper for the state of the call (SUCCESS, ERROR, LOADING).

And finally the Transformer:

private fun <Data> getRetryTransformer(): SingleTransformer<Response<Data>, Response<Data>> {
    return SingleTransformer { singleResponse ->
        singleResponse
                .onErrorReturn {
                    singleResponse.blockingGet()
                }
                .retryWhen { errors ->
                    errors.zipWith(retrySubject.toFlowable(BackpressureStrategy.LATEST),
                            BiFunction<Throwable, Boolean, Flowable<Throwable>> { throwable: Throwable, isRetryEnabled: Boolean ->
                                if (isRetryEnabled) {
                                    Flowable.just(null)
                                } else {
                                    Flowable.error(throwable)
                                }
                            })
                }
    }
}

The retrySubject:

private val retrySubject = PublishSubject.create<Boolean>()

And when the user clicks the retry button, I call:

retrySubject.onNext(true)

The problem is now, that the error is not returned to the ViewModel and the SnackBar is never shown. I tried onErrorResumeNext() as well with no success. The whole retryWhen/zipWith part seem to work. Because in the repository there some more API calls with no retry behavior (yet) and there the SnackBar is displayed. That means, I do anther call where the SnackBar is shown -> button click and the retry transform works as expected.

If you need some more information please don't hesitate to ask! Any help is appreciated!


Solution

  • Strange, as soon you do it the right way, it works. I over read somehow that I need in doOnError{...} to manage to show my Snackbar.

    Here is my working retry transformer:

    private fun <Data> getRetryTransformer(): SingleTransformer<Response<Data>, Response<Data>> {
        return SingleTransformer { singleResponse ->
            singleResponse
                    .doOnError {
                        errorEventSubject.onNext(it)
                    }
                    .retryWhen { errors ->
                        errors.zipWith(retrySubject.toFlowable(BackpressureStrategy.LATEST),
                                BiFunction<Throwable, Boolean, Flowable<Throwable>> { throwable: Throwable, isRetryEnabled: Boolean ->
                                    if (isRetryEnabled) {
                                        Flowable.just(throwable)
                                    } else {
                                        Flowable.error(throwable)
                                    }
                                })
                    }
        }
    }
    

    And the chain looks now like this (and I think it's beautiful):

    override fun loadMyData(): Observable<Resource<DataResponse>> {
        return retrofitApi
                .getMyData()
                .compose(getRetryTransformer())
                .toObservable()
                .compose(getResponseTransformer())
    }
    

    What else I needed to propagate the error to my ViewModel is a 2nd PublishSubject:

    private val errorEventSubject = PublishSubject.create<Throwable>()
    

    And in the ViewModel I observe the changes for it and show the Snackbar. That's it.