RxJava's retryWhen operator - android

I'm trying to understand retryWhen operator in depth and I have some code as below.
Flowable.just(1, 2, 3, 4, 5)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen { throwable ->
Log.d("Debug", "retryWhen proceed...")
throw Exception("There is a exception")
}
.subscribe(
{ item ->
Log.d("Debug", "success : $item")
},
{ throwable ->
Log.d("Debug", "error : ${throwable.message}")
},
{
Log.d("Debug", "complete")
}
)
And the result is shwon as below.
Debug: retryWhen proceed...
Debug: error : There is a exception
The question is that when retryWhen operator is triggered?
I assume retryWhen operator will be triggered only when there is a exception occurs.
But the result is not what I thought obviously,
Any thoughts on this? Thanks!

retryWhen { errors -> ... } take an Observable<Throwable> and should return an Observable that return anything for retrying or an error for stop retrying.
One example could be:
.retryWhen(attempts -> {
return attempts.zipWith(Observable.range(1, 3), (n, i) -> i).flatMap(i -> {
System.out.println("delay retry by " + i + " second(s)");
return Observable.timer(i, TimeUnit.SECONDS);
});
})
(taken from http://reactivex.io/documentation/operators/retry.html)
This code will delay each retry.
By the way, throwing an exception is not the thing to do in this method.
Documentation:
* Great blog article that explained the retryWhen

Related

How can RxJava2's onErrorResumeNext counterpart in Kotlin Flow that returns another flow be implemented?

I want to implement offline-last approach with Flow, first try to fetch data from remote source if it fails, for instance Retrofit throwing network exception, i want to fetch data from local source with the code below
return flow { emit(repository.fetchEntitiesFromRemote()) }
.map {
println("🍏 getPostFlowOfflineLast() First map in thread: ${Thread.currentThread().name}")
val data = if (it.isEmpty()) {
repository.getPostEntitiesFromLocal()
} else {
repository.deletePostEntities()
repository.savePostEntity(it)
repository.getPostEntitiesFromLocal()
}
entityToPostMapper.map(data)
}
.catch { cause ->
println("❌ getPostFlowOfflineLast() FIRST catch with error: $cause, in thread: ${Thread.currentThread().name}")
flow { emit(repository.getPostEntitiesFromLocal()) }
}
.map { postList ->
println("🎃 getPostFlowOfflineLast() Second map in thread: ${Thread.currentThread().name}")
ViewState<List<Post>>(
status = Status.SUCCESS,
data = postList
)
}
.catch { cause: Throwable ->
println("❌ getPostFlowOfflineLast() SECOND catch with error: $cause, in thread: ${Thread.currentThread().name}")
flow {
emit(
ViewState<List<Post>>(
Status.ERROR,
error = cause
)
)
}
}
But it gets stuck with exception
I: ❌ getPostFlowOfflineLast() FIRST catch with error: java.net.UnknownHostException: Unable to resolve host "jsonplaceholder.typicode.com": No address associated with hostname, in thread: main
What should be the right implementation to have any observable like with RxJava onResumeNext if repository function was an Observerable?
onErrorResumeNext { _: Throwable ->
Observable.just(repository.getPostEntitiesFromLocal())
}
Figured out that i can use emitAll with a flow to continue flow even multiple times.
.catch { cause ->
println("❌ getPostFlowOfflineLast() FIRST catch with error: $cause, in thread: ${Thread.currentThread().name}")
emitAll(flow { emit(repository.getPostEntitiesFromLocal()) })
}
.map {
if (!it.isNullOrEmpty()) {
entityToPostMapper.map(it)
} else {
throw EmptyDataException("No data is available!")
}
}
.map { postList ->
println("🎃 getPostFlowOfflineLast() Third map in thread: ${Thread.currentThread().name}")
ViewState(status = Status.SUCCESS, data = postList)
}
.catch { cause: Throwable ->
println("❌ getPostFlowOfflineLast() SECOND catch with error: $cause, in thread: ${Thread.currentThread().name}")
emitAll(flow { emit(ViewState(Status.ERROR, error = cause)) })
}

How to parse error response to Throwable?

I am using Retrofit and RxJava to make network requests like this:
How I am declaring request:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Single<Response<Void>>
How I am calling:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful)
Log.d("----------", "Success!")
else
Log.d("----------", "Not Successfull!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
Some code have been dropped for readability. The probem is even though I get responses back with 401 or 400 statuses, doOnSuccess is being called. Should not the doOnError be called here? I am confused.
As a result my logact is showing "Not Successful" message. How can make sure that doOnErro is called when I get responses back with 401 or 400 statuses?
Or can I parse the incoming response to Throwable and call doOnError() function?
Change the Retrofit API call to return Completable:
#POST("auth/profile/edit/")
fun updateProfile(#Body body: ProfileUpdateBody): Completable
then handle the "success case" via doOnComplete:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnComplete {
Log.d("----------", "Success!")
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({ }, { })
The real question is, why would you want to throw and exception when the request fails?
The correct processes are being followed here, doOnSuccess is being called as intended because the request has returned a response without encountering an exception being thrown. Regardless of whether the request's response is successful or not.
You should handle the state of your response accordingly and not throw arbitrary exceptions for it:
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.isSuccessful()) {
// handle success
} else {
// handle failure
}
}, t -> {
// handle thrown error
yourErrorHandlerMethod(t);
})
The response you getting is correct, the response is shown in doOnSuccess cuz the API you hitting got successfully hit, no matter what was the response code.
doOnError is called when actual API call is failed like network drop in the middle or some server-side issues.
Or can I parse the incoming response to Throwable and call doOnError() function?
You cant do this instead, you can handle the response in doOnSuccess as
try {
api.updateProfile(**some data**)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Log.d("----------", "Subscribed!")
}
.doOnSuccess {
if(it.isSuccessful) // responce code = 200/201
Log.d("----------", "Success!")
else if (it.responseCode == 400 ){
Log.d("----------", "Not Found!")
// Call a method that handles this according to your requirement.
PageNotFoundHandler();
// OPTIONAL throw new UserException();
}
else if (it.responseCode == 401 ){
Log.d("----------", "Not Authorised!")
// Call a method that handles this according to your requirement.
TokenExpiredHandler(); //OR
UnAuthorizedAccessHandler();
// OPTIONAL throw new UserException();
}
else {
Log.d("----------", "Some another Error!")
// Call a method that handles this according to your requirement.
// OPTIONAL throw new UserException();
}
}
.doOnError {
Log.d("----------", "Error Happened!")
}
.subscribe({
}, {
})
} catch
{
ErrorHandler();
}
Or can I parse the incoming response to Throwable and call doOnError() function?
As you mention that you want a throwable, you can achieve it by using the try-catch block.
Just throw a custom EXCEPTION, you have to create a new Custom Exception class for it.

How to execute many RxJava2 flux in a row

I'm introducing myself about RxJava2, but i feel like i'm doing something wrong. In my case, i want to do some following asynchronous actions.
In this example, the first action is to check if the device is connected (wifi or data, let's admit it take time), then i want to connect to an api and then i want to do a http call for get a list (observable) and then work with it. If one of those operation fail, an onError or exception should be raised and handled in the subscribe.
I have this code who works:
Single.create((SingleEmitter<Boolean> e) -> e.onSuccess(Connectivity.isDeviceConnected(MainActivity.this)) )
.subscribeOn(Schedulers.io())
.flatMap(isDeviceConnected -> {
Log.i("LOG", "isDeviceConnected : "+ isDeviceConnected);
if(!isDeviceConnected)
throw new Exception("whatever"); // TODO : Chercher vrai erreur
return awRepository.getFluxAuthenticate(host, port, user, password); // Single<DisfeApiAirWatch>
})
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
But do a single who emmit a boolean for a simple "if" sounds wrong. A Completable seems more logical (work or not, continue or stop). I tried with the following code but it's not working.
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete(); // Guess not good, should call the complete of subscribe ?
else
e.onError(new Exception("whatever"));
} ).toObservable()
.subscribeOn(Schedulers.io())
.flatMap(awRepository.getFluxAuthenticate(host, port, user, password)) //Single<DisfeApiAirWatch>
.toObservable()
.flatMap(awRepository::getFluxManagedApps) // List of apps : Observable<AirwatchApp>
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);
How to make this code work ?
I know i can do a first subscribe on the complatable and in the "onSuccess" of this one write another flux / the rest of the code. But i don't think stack flows inside each other is a good solution.
Best regards
Completable has no value so flatMap will never be invoked. You have to use andThen and make the authentication success value the input for the subsequent flatMap:
Completable.create((CompletableEmitter e) -> {
if(Connectivity.isDeviceConnected(MainActivity.this))
e.onComplete();
else
e.onError(new Exception("whatever"));
})
.subscribeOn(Schedulers.io())
.andThen(awRepository.getFluxAuthenticate(host, port, user, password)) // <-----------
.flatMapObservable(awRepository::getFluxManagedApps)
.observeOn(AndroidSchedulers.mainThread())
.doFinally(this::hideProgressDialog)
.subscribe(
app -> Log.i("LOG", "OnNext : "+ app),
error -> Log.i("LOG", "Error : " + error),
() -> Log.i("LOG", "Complete : ")
);

Throwing captured exception results in CompositeException

In the following code, I get an EmptyResultSetException when no more records are read from the Sqlite database. This is expected. This exception is caught in doOnError. If I throw this exception, the exception caught in the subscriber's onError handler becomes a CompositeException. The CompositeException contains a list of exceptions and in this case it only contains a single exception which happens to be the EmptyResultSetException. Originally, before changing my code, the EmptyResultSetException was sent directly to the subscriber's onError handler. Now it gets placed into a CompositeException. What could be causing this?
val msgToSendPublisher = BehaviorSubject.createDefault(MessageToSend())
msgToSendPublisher
.flatMap { _ ->
App.context.repository.getMessageToSend().flatMapObservable { messageToSend -> Observable.just(messageToSend) }
}
.doOnError { error ->
throw error
}
.doOnNext {
msgToSendPublisher.onNext(it)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ messageToSend ->
},
{ ex ->
if (ex !is EmptyResultSetException) {
}
},
{
}
)

Why debounce() with toList() doen't working in RxAndroid?

While I'm using debounce() ,then fetch data from backend and the data
I want to convert to another data and lastly use toList().
when I'm using toList() nothing happens no any log not in subscribe and error ,without toList() it works and subscribe() method enters as much as I have list of books, I tested the second part of code it without debounce() just getItems() and using toList() it works.
Below is my code the first part with debounce() and itList() which is not working and the second with toList() which works
public Flowable<List<Book>> getItems(String query) {}
textChangeSubscriber
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s -> getItems(s).toObservable())
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
getItems(query).flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(books -> {
Log.i("test", "" + "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});
toList requires the sequence to terminate which doesn't happen on the outer stream that responds to text events. You should move the processing of the books into the switchMap:
textChangeSubscriber
.map(CharSequence::toString) // <-- text components emit mutable CharSequence
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.computation())
.switchMap(s ->
getItems(s)
.flatMapIterable(items -> items)
.map(Book::convert)
.toList()
.toFlowable() // or toObservable(), depending on textChangeSubscriber
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
Log.i("test", "" + books.toString());
}, error -> {
Log.i("test", "" + error);
});

Categories

Resources