So I am currently chaining API calls together using flatMap and it is working very well for my use cases. If one of my calls return a failing response code, then I pass an error single that contains a throwable with a message that says which call failed and it keeps going. Here is how I'm doing it now:
dataManager.apiCall1(dataManager.sessionId!!)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.flatMap{apiCall1Response ->
if (apiCall1Response.isSuccessful && apiCall1Response.body() != null) {
// First api call was successful, execute api call 2.
return#flatMap dataManager.apiCall2(apiCall1Response.importantVal)
} else {
// First api call failed
return#flatMap Single.error<Throwable>(Throwable("First api call failed."))
}
}.flatMap{apiCall2Response ->
if (apiCall2Response != null && apiCall2Response.isSuccessful && apiCall2Response.body() != null) {
// Second api call was successful, execute api call 3.
return#flatMap dataManager.apiCall3(apiCall2Response.importantVal)
} else if (apiCall2Response is Throwable) {
// Api Call 1 Failed.
return#flatMap Single.error<Throwable>(apiCall2Response)
} else {
// Second api call failed
return#flatMap Single.error<Throwable>(Throwable("Second api call failed."))
}
}.subscribe({apiCall3Response ->
if (apiCall3Response is Response<*> && apiCall3Response.body() != null) {
// Success!
navigator?.successful(response)
} else if (apiCall3Response is Throwable) {
// Something failed from before.
navigator?.handleError(apiCall3Response)
} else {
// Third api call failed, handle error
navigator!!.handleError(Throwable("Api call 3 failed."))
}
}, {throwable ->
navigator!!.handleError(throwable)
})
Well, now I am realizing that I need to make a different api call if my first api call is successful and any of my other calls fail. This is a sequence of calls to log a user in, if the login call is successful, but the next call fails, we would need to call the api logout endpoint. I know it is bad practice to create another single inside of the subscribe() method, so I don't want to do that. I would rather pass the logout call through, but the problem is that there is no way of knowing which api call is being returned in the subscribe method since both the logout and apiCall3 return empty bodies. I would also like to call the logout endpoint if apiCall3 fails, but not sure if that is possible. Here is what I am trying to do:
dataManager.apiCall1(dataManager.sessionId!!)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.flatMap{apiCall1Response ->
if (apiCall1Response.isSuccessful && apiCall1Response.body() != null) {
// First api call was successful, execute api call 2.
return#flatMap dataManager.apiCall2(apiCall1Response.importantVal)
} else {
// First api call failed
return#flatMap Single.error<Throwable>(Throwable("First api call failed."))
}
}.flatMap{apiCall2Response ->
if (apiCall2Response != null && apiCall2Response.isSuccessful && apiCall2Response.body() != null) {
// Second api call was successful, execute api call 3.
return#flatMap dataManager.apiCall3(apiCall2Response.importantVal)
} else if (apiCall2Response is Throwable) {
// Api Call 1 Failed.
return#flatMap Single.error<Throwable>(apiCall2Response)
} else {
// Second api call failed, logout
return#flatMap dataManager.logoutApiCall()
}
}.subscribe({apiCall3OrLogoutResponse ->
// I would like to be able to determine which call this response is from. That is the question.
if (apiCall3OrLogoutResponse is Response<*> && apiCall3OrLogoutResponse.body() != null) {
// Success!
navigator?.successful(response)
} else if (apiCall3OrLogoutResponse is Throwable) {
// Something failed from before.
navigator?.handleError(apiCall3OrLogoutResponse)
} else {
// Third api call or logout call failed, handle error
if (apiCall3OrLogoutResponse is ApiCall3) {
// Api Call 3 failed.
// Somehow call logout api endpoint
} else if (apiCall3OrLogoutResponse is LogoutCall {
// Logout call failed.
navigator?.handleError(Throwable("Logout failed."))
}
}
}, {throwable ->
navigator!!.handleError(throwable)
})
Is there a better way to do this? My use cases are making three sequential api calls, if the first one fails, send a throwable to the subscriber, if the first one succeeds and any fail after that, make another api call.
I figured it out by throwing custom exceptions instead of passing Single.error down and having my final call checked in flatMapCompletable rather than in the subscription. Then I called the logout endpoint in doOnError if the exception isn't a login exception.
dataManager.apiCall1(dataManager.sessionId!!)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.flatMap{apiCall1Response ->
if (apiCall1Response.isSuccessful && apiCall1Response.body() != null) {
// First api call was successful, execute api call 2.
return#flatMap dataManager.apiCall2(apiCall1Response.importantVal)
} else {
// First api call failed
throw ApiCall1Exception("Api Call 1 failed.")
}
}.flatMap{apiCall2Response ->
if (apiCall2Response != null && apiCall2Response.isSuccessful && apiCall2Response.body() != null) {
// Second api call was successful, execute api call 3.
return#flatMap dataManager.apiCall3(apiCall2Response.importantVal)
} else {
// Second api call failed
throw Throwable("Api call 2 failed.")
}
}.flatMapCompletable{apiCall3Response ->
if (apiCall3Response.body() != null) {
// All api calls were successful!
Completable.complete()
} else {
// Third api call failed.
throw Throwable("Api call 3 failed.")
}
}.doOnError{throwable ->
if (throwable !is ApiCall1Exception) {
// Api call 1 was successful, but something else failed, call logout endpoint.
dataManager.logout()
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
}
}.subscribe({
// Success!
navigator?.success()
}, {throwable ->
// Something failed!
navigator?.handleError(throwable)
})
Related
i am using live data in android. But my problem is that the data is received at last after every function is called. In my case one of my function has dependency on live data but it is called before live data is received.i have added comments to make u understand better. Please help.
// polist is a MutableList
transactionDao.selectAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
}
})
vregularDao.getAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
}
})
// but this is called first then above codes.I want this to be called only after live data is received
alllist.forEach{
//perform some action
}
You may want to look into threads. The code below runs in a thread and once there's a return value it is added to polist
transactionDao.selectAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
}
})
Same for
vregularDao.getAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
}
})
Therefor the last chunk of code is called directly. as the other two thread hasn't returned any data at the moment.
You would need to create some sort of blocker.
So it would be call another function after the list enumeration, this isn't elegant(PSUDEOCODE)...
bool selectAllDone;
bool getAllDone;
// polist is a MutableList
transactionDao.selectAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
performSomeAction();
selectAllDone = true;
}
})
vregularDao.getAll().observe(this, Observer {
if (it != null && it.isNotEmpty()) {
polist.addAll(it)
performSomeAction();
getAllDone = true;
}
})
// but this is called first then above codes.I want this to be called only after live data is received
public fun performSomeAction(){
if(getAllDone & selectAllDone){
alllist.forEach{
//perform some action
}
}
}
I have an API call which verifies some status against an "Id". The API returns Single or error. I have a list of such Id's, Only one Id is valid to return success or none (all id's return error). What I need is, Iterate through each Id and skip the errors from API call, until either a success or end of the list. I am able to achieve this sequentially. However, I am trying to do the same, using ParallelFlowable.
It works fine when an Id returns success, But when there is no id which returns success (all ids fail), then it just skip all the errors from API, but does not notify the subscriber after all the ids are validated. I am not sure how to handle this.
// API call
fun getStatus(Id: String): Single<String> {
//... returns Single<String> or error
}
//Sequential flow, Working
fun getStatus(ids: List<String>): Single<String> {
Observable.fromIterable(ids)
.flatMapSingle { id ->
getStatus(id)
.onErrorResumeWith { singleSource ->
if (ids.last() == id)) { //If this is last item in list, return error
singleSource.onError(NoStatusFoundException())
} else {
// Skip errors until valid id is found or till the list reached end.
Flowable.empty<String>()
}
}
}.firstOrError()
}
// Parallel Flow, How to identify the list is completed and return NoStatusFoundException in case of all id's fail?
fun getStatus(ids: List<String>): Single<String> {
Flowable.fromIterable(ids)
.parallel()
.runOn(io())
.flatMap{ id -> getStatus(id).toFlowable()
.onErrorResumeWith { Flowable.empty<String>() }
}
.sequentialDelayError()
.firstOrError()
.onErrorResumeNext { Single.error(it) }
}
// Subscription
getStatus(listOf("1","2","3","4","5",))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscriber({ id->
// success
this is notified when an id is success
},
{ // error handler - Need help here
Never notified when all the id's fail?
})
I am able to resolve this issue by removing onErrorResumeWith { Flowable.empty<String>() } within flatMap and implementing RxJavaPlugins.setErrorHandler{...}.
sequentialDelayError() delays all the errors until all the rails have finished their task.
fun getStatus(ids: List<String>): Single<String> {
Flowable.fromIterable(ids)
.parallel()
.runOn(io())
.flatMap{ id -> getStatus(id).toFlowable()
}
.sequentialDelayError()
.firstOrError()
.onErrorResumeNext { Single.error(it) }
}
///
RxJavaPlugins.setErrorHandler{ it: Throwable ->
is UndeliverableException -> {...}
.....
}
You are returning Flowable.empty() that immediately completes the subscription. Taken from the docs:
Returns a Flowable that emits no items to the {#link Subscriber} and immediately invokes its {#link Subscriber#onComplete onComplete} method.
Maybe you can return Flowable.just("") or provide some expected argument incase of an error.
.onErrorResumeWith { Flowable.just("") }
The problem is in this line:
.onErrorResumeWith { Flowable.empty<String>() }
The parameter of onErrorResumeWith is a Publisher<out T>, not () -> Publisher<out T>. The Publisher interface happens to have a single method, void subscribe​(Subscriber<? super T> s). As such, it is eligible for SAM conversion.
The lambda { Flowable.empty<String>() } is a perfectly valid (Subscriber<String>) -> Unit that ignores its single parameter, calls a method, and ignores the result. This compiles, but the result is for all practical purposes the same as Flowable.never().
Instead of a lambda, you need to pass Flowable.empty() directly into onErrorResumeNext():
.flatMap{ id -> getStatus(id).toFlowable()
.onErrorResumeWith(Flowable.empty<String>())
}
So I am using flatMap with RxJava to chain some API calls that return Singles and was wondering how to pass a custom throwable to the subscribe method in the flatmap method depending on my own condition.
This is in an Android Kotlin app that uses MVVM and RxJava2/RxAndroid for data operations. I am trying to chain together a bunch of API calls that return Singles using the flatmap method. Everytime, I want to call a new API method after the previous one, I want to check to see if the previous call's response was successful. If the previous call's response was successful, I just call the next API method in the flatmap and everything is good, but if the previous call's response was not successful, I want to pass a custom throwable that tells me where and why the operation wasn't successful. Right now in the situation of an unsuccessful response, I pass a Single with a null value, but that just gives me a null pointer when subscribing and that isn't very helpful.
dataManager.apiCall1(dataManager.sessionId!!)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.flatMap{apiCall1Response ->
if (apiCall1Response.isSuccessful && apiCall1Response.body() != null) {
// First api call was successful, execute api call 2.
return#flatMap dataManager.apiCall2(apiCall1Response.importantVal)
} else {
// First api call failed
Single.just(null)
}
}.flatMap{apiCall2Response ->
if (apiCall2Response != null && apiCall2Response.isSuccessful && apiCall2Response.body() != null) {
// Second api call was successful, execute api call 3.
return#flatMap dataManager.apiCall3(apiCall2Response.importantVal)
} else {
// Second api call failed
Single.just(null)
}
}.subscribe({apiCall3Response ->
if (apiCall3Response != null && apiCall3Response.body() != null) {
// Success!
navigator!!.successful(response)
} else {
// Third api call failed, handle error
navigator!!.handleError(Throwable("Api call 3 failed."))
}
}, {throwable ->
// Failure, this is where I want to receive a custom throwable
// in case one of the responses were unsuccessful.
navigator!!.handleError(throwable)
})
I expect the throwable message to be something like "apiCall1" failed, but the throwable message when apiCall1 fails is just NullPointer because of the Single.just(null) that I pass.
Instead of Single.just(null) use Single.error().
For example: Single.error<TypeHere>(RuntimeException("First api call failed"))
For the most readable code, define an extension function like this
private fun <T> Single<Response<T>>.decorateWithErrorHandling(): Single<T> {
return this
.onErrorResumeNext { throwable ->
// Map network layer exceptions, e.g. IOException to your specific "domain level" exceptions
Single.error(mapNetworkErrorToSpecificThrowable(throwable))
}
.flatMap { response ->
if (response.isSuccessful) {
Single.just(response.body())
} else {
Single.error(convertToSpecificThrowable(response))
}
}
}
Use it like this:
apiCall1()
.composeWithErrorHandling().flatMap { result1 -> apiCall2(result1) }
.composeWithErrorHandling().flatMap { result2 -> apiCall3(result2) }
etc.
Use this code
throw NullPointerException from flatMap
and catch the error by
.onErrorReturn(throwable -> {//your work here})
#Carson J.
I'm trying to recover from errors using RxJava and GRPC. This is my observable:
Observable<Object> observable = Observable.fromCallable(() -> {
try {
Grpc.MyRequest request = Grpc.MyRequest.newBuilder()
.setToken(mToken)
.build();
Grpc.MyResponse reply = mStub.mytest(request);
return reply;
} catch (Exception e) {
///
}
}).cache();
And this is the subscription:
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> {
})
.subscribe((result) -> {
MyResponse res = ((MyResponse) result);
if (res.getCode()!=0) {
//Check error code and try to refresh token and repeat this request after.
}
},throwable -> {
throwable.printStackTrace();
});
So, when I get the error from my GRPC service, depending on the error code, I want to try and recover from it by doing another request, and then repeating the original request. I'm not sure how to use RxJava retrywhen.
What is the most elegant way of doing something like this?
Error recovery in an observer chain does require a bit of tap dancing, and is by no means elegant. However, it can be contained in the observer chain.
boolean isRecoverable( Throwable t ) {
// this test can be as sophisticated as you want
if ( t instanceof StatusRuntimeException ) {
return true;
}
return false;
}
...
.retryWhen( throwableObservable ->
throwableObservable.flatMap( t -> isRecoverable( t )
? Observable.just("")
: Observable.error( t ) )
...
This approach allows you to decide what you want to do with the error. You could add a delay the just() so that you don't retry immediately. Instead of the just(), you could return an Observable that fetches a new API token.
I`m trying to implement a autoLogin function with retry when and need to change the parameters of the previous observable after the auto login is done.
So i created a function
class TestClass(): Function<Flowable<out Throwable>, Flowable<*>> {
override fun apply(flowable: Flowable<out Throwable>): Flowable<*> {
return flowable
.flatMap {
if (it is HttpException && it.code() == 401 ) {
Timber.d("apply: Doing AutoLogin.")
dataSource
.login(document, password)
.map {
//Auto login done, update user
}
.doOnError({
// login failed
})
.toFlowable()
} else {
Flowable.error(it)
}
}
}
}
And apply it this way:
dataSource.apiCall(user)
.retryWhen(TestClass())
.subscribe()
Tried this way too:
Single.defer {
dataSource.apiCall(user)
.retryWhen(TestClass())
}
.subscribe()
}
The problem is, after the login when the request is retried, the user isn`t updated.
What i`m doing wrong?