Here is a code with network call on every period of time. If network call fails then interval stops repeating
if (userManager.isLoggedIn()) {
notificationsRefreshSubscription = Observable
.interval(0, 10, TimeUnit.SECONDS)
.subscribeOn(ioScheduler)
.flatMap {
return#flatMap service.checkForNewNotifications(
token = userManager.getUser().takeToken(),
locale = userManager.getUserLanguageCode())
}
.observeOn(uiScheduler)
.subscribe({
listener?.onNewNotificationsReceived(it.count)
}, {
// Empty
})
}
how can I continue interval repeating even on error?
With help of #akarnokd Answer is:
return#flatMap service.checkForNewNotifications(
token = userManager.getUser().takeToken(),
locale = userManager.getUserLanguageCode())
.onErrorResumeNext(Observable.empty())
all the magic was to put .onErrorResumeNext(Observable.empty()) inside .flatmap()
Related
I have a network call I make as part of a function that fetches the timer value for how long this data is going to be alive (basically the ttl of an object). I need to retrigger the same function as soon as the timer ends.
fun refresh() {
service.makeNetworkCall()
.subscribe { response ->
val ttl = response.ttl
retriggerAgainAfterTtlExpires(ttl)
}
I'm currently retriggering the function in the .doOnNext() call as shown below. But this doesn't chain the observable to the original one. It creates a whole new process and I want to avoid it.
fun retriggerAgainAfterTtlExpires(ttl:Long) {
Observable.interval(ttl, ttl, TimeUnit.SECONDS)
.doOnNext { refresh() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
How can I retrigger this function without having to call .doOnNext()
Save the TTL in a field and use deferred creation of the initial delay. At the end, simply use repeat.
private long ttl = 1000 // initial TTL
Observable.defer(() -> {
return Observable.just(ttl).delay(ttl, TimeUnit.MILLISECONDS);
})
.flatMap(v -> service.networkCall())
.doOnNext(response -> { ttl = response.ttl; })
.observeOn(mainThread())
// handle other aspects of response
.repeat()
.subscribe(/* ... */);
I have following code:
repo.getObservable()
.timeout(1, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
_isInProgress.value = true
}
.doFinally {
_isInProgress.value = false
}
.subscribe(
{
Timber.d("Success")
},
{
Timber.e(it)
})
.trackDisposable()
The problem with it is that I'm successfully getting Success message after couple seconds, yet my preloader still waits for 1 minute and then my error part of subscribe gets executed. Is that expected behaviour? What can I do to stop timeout if success part of subscribe gets executed?
P. S. Observable returned from getObservable() is created like this: PublishSubject.create()
If you need one result, use take(1) before or after timeout:
repo.getObservable()
.take(1) // <---------------------------------------------
.timeout(1, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
_isInProgress.value = true
}
.doFinally {
_isInProgress.value = false
}
.subscribe(
{
Timber.d("Success")
},
{
Timber.e(it)
})
.trackDisposable()
That must be because you are not calling onComplete on the PublishSubject and only onNext is called.
See this example:
PublishSubject<Integer> source = PublishSubject.create();
// It will get 1, 2, 3, 4 and onComplete
source.subscribe(getFirstObserver());
source.onNext(1);
source.onNext(2);
source.onNext(3);
// It will get 4 and onComplete for second observer also.
source.subscribe(getSecondObserver());
source.onNext(4);
source.onComplete();
Until onComplete is called the observers are waiting for more results.
You can either unsubscribe/dispose after the reception of the result you were waiting for or call onComplete on the Observable when all the results have been sent.
I'm using a travel API which I first hit with a request to create a session and then I use the session URL returned from that URL to then call until its status parameter returns UpdatesComplete.
Here's what I have so far:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.doOnNext {
if (it.status != "UpdatesComplete") travelInteractor.pollResults(pollUrl)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
}
.subscribe({
// Subscription stuff
)}
What's currently happening is that it will call doOnNext() and then it will make the network poll but I won't be capturing the subscription and also won't chain another poll. Is there a more efficient way I can be writing this?
Solution
Thanks to iagreen I managed to achieve this with:
lateinit var pollUrl: String
travelInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
travelInteractor.pollResults(url)
.retryWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.repeatWhen {
it.delay(1000, TimeUnit.MILLISECONDS)
}
.filter {
it.itineraries.map { ... } // Use response here appropriately and then check status
it.status == "UpdatesComplete"
}
.take(1)
}
.subscribe({
// Subscription stuff
)}
I am assuming your pollResults(url) method returns a Single or an Observable that behaves like a single -- it returns one result and then onComplete. If that is truly the case, you can use repeatWhen to retry the request on success and retryWhen to retry on error. See the code below.
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
skyScannerInteractor.pollResults(url)
.repeatWhen { complete -> complete.delay(1, TimeUnit.SECONDS) }
.retryWhen { errors -> errors.delay(1, TimeUnit.SECONDS) }
.filter({ it.status == "UpdatesComplete" })
.take(1) // Take the first valid value and complete
}
.subscribe({
// Subscription stuff
)}
A little explanation -
repeatWhen/retryWhen will try the request every second.
filter will cause elements with the wrong status to be ignored.
When you get the first status == "UpdatesComplete" element, take(1) will emit that value and complete -- this will have the effect of cancelling the retries.
Note: In the case of an error, retrying a network request forever is usually the wrong thing to do. I recommend you modify the retryWhen above to suit you use case to terminate in the event of network failure. For example, you could retry three times and then propagate the error. See this article for some examples on how you could do that. It is also a good reference on repeatWhen/retryWhen.
retry() and retryWhen() both respond to an onError event in the Observable, that is why it isn't actually retrying; you're not receiving those onError events in your pollResults() Observable. Right now, your retrying code doesn't actually depend on the JSON response.
There are two ways I would think about going about this:
Throw an exception in your pollResults() Observable if the JSON response is unsatisfactory. This should trigger the retryWhen(). You'll need to test for it in the Observable somewhere.
Reorganize your Observable like this:
`
lateinit var pollUrl: String
skyScannerInteractor.createSession("LHR", "AKL", "2018-04-20", "2018-04-22")
.doOnSubscribe {
loading.postValue(true)
}
.flatMap { url ->
pollUrl = url
skyScannerInteractor.pollResults(pollUrl)
}
.doOnNext {
if (it.status != "UpdatesComplete") {
throw IOException("Updates not complete.") //Trigger onError
}
}
.retryWhen { //Retry the Observable (createSession) when onError is called
it.delay(1000, TimeUnit.MILLISECONDS)
}
.subscribe({
// Will give result only when UpdatesComplete
}
I am looking to poll the backend call for certain number of times for a predefined regular intervals. I would like to exit the loop if I have received an expected payload in between the loop and update the UI else terminate the polling.
Below is the code I normally do when I make standard http call.
//Response Model from backend API
public class ApplicationStatusResponse
{
public boolean isActive;
}
//Retrofit facade
#POST(v1/api/applicationStatus)
Single<ApplicationStatusResponse> checkApplicationStatus(#Body ApplicationStatusRequest applicationRequest);
-----
DisposableSingleObserver<ApplicationStatusResponse> disposableSingleObserver = new DisposableSingleObserver<ApplicationStatusResponse>() {
#Override
public void onSuccess(ApplicationStatusResponse response) {
// Update UI Here
}
#Override
public void onError(Throwable e) {
}
};
CompositeDisposable compositeDisposable = new CompositeDisposable();
// Following call works alaways works
DisposableSingleObserver<ApplicationStatusResponse> disposable = originationRepo.checkApplicationStatus(applicationStatusRequest)
.observeOn(schedulerProvider.mainThread())
.subscribeWith(disposableSingleObserver);
compositeDisposable.add(disposable);
But I am kind of lost here in the following code with the syntax error and I am not able to use the same disposableSingleObserver when calling from the Flowable.interval and need help with my use case where I need to update the UI the status regularly until the time is elapsed or status is active which ever happens first and also I am not after terminating the polling if I received HTTP Status Code of 500 instead repeat until the mentioned conditions are met.
//Help Needed here when I need polling in regular interval - I am kind of the syntax error complain from Android Studio
int INITIAL_DELAY = 0;
int POLLING_INTERVAL = 1000;
int POLL_COUNT = 8;
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) ??
// How can I receive the response payload and update the UI
compositeDisposable.add(disposable);
Appreciate your help in advance.
(in continuation with MyDogTom's answer you could also "short-circuit" the observable by throwing a custom Error/Exception)
Option 3:
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest)) // .flatMap (?)
.take(POLL_COUNT) //YES
.doOnNext() // update UI here
.map(response -> {
if(!response.checkCondition()) {
throw new ShortCircuitException();
}
return response.data();
})
.onErrorResumeNext(throwable -> (throwable instanceof ShortCircuitException)
? Observable.empty()
: Observable.error(throwable))
Option #1 Use filter + take(1)
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) //YES
.doOnNext() // update UI here
.map(response -> ) // should stop condition. true - stop, false - continue
.filter(!shouldContinue)
.take(1)
Option #2 Use Subject + takeUntil
Subject<Boolean> stopSubject = PublishSubject.create();
disposable = Flowable
.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS)
.takeUntil(stopSubject.asObservable())
.map(x -> originationRepo.checkApplicationStatus(applicationStatusRequest))
.take(POLL_COUNT) //YES
.subscribe(
response -> {
//update UI
boolean shouldStop = ... // calculate
if (shouldStop) {
stopSubject.onNext(true);
}
}
...
)
PS. This is pseudo code. I hope you get idea.
I'm trying to create an Observable such that it will load some data from the network on an interval, and whenever the user refreshes the page. This is the gist of what I have so far:
PublishSubject<Long> refreshSubject = PublishSubject.create();
Observable<MyDataType> observable = Observable.merge(
Observable.interval(0, 3, TimeUnit.SECONDS),
refreshSubject
)
.flatMap(t -> {
// network operations that eventually return a value
// these operations are not observables themselves
// they are fully blocking network operations
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
// update ui with data
}, error -> {
// do something with error
});
Later in a refresh callback I have:
refreshSubject.onNext(0L);
It runs on the interval fine, however when I refresh, it explodes with a NetworkOnMainThreadException. I thought that I handled this with subscribeOn/observeOn. What am I missing? Also, why doesn't this cause a crash when the Observer is triggered from the interval?
You have to change your subscribeOn(Schedulers.io()) to observeOn(Schedulers.io()) and move it over your flatMap.
The reason for this is that your refreshSubject is a PublishSubject, which is an Observable and an Observer.
Since the onNext() of this PublishSubject is called inside the intern Observable first before the result gets delivered to your subscription.
This is also the reason that it works when you just use your Observable(and the fact that interval always subscribes to the computation thread by default).
Just check the output of those two snippets:
Observable.merge(
Observable.interval(0, 3, TimeUnit.SECONDS),
refreshSubject
)
.observeOn(Schedulers.io())
.doOnNext(aLong -> Log.d("Thread", Thread.currentThread().toString()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
Log.d("Subscribe Thread", Thread.currentThread().toString());
}, error -> {
// do something with error
});
vs
Observable.merge(
Observable.interval(0, 3, TimeUnit.SECONDS),
refreshSubject
)
.doOnNext(aLong -> Log.d("Thread", Thread.currentThread().toString()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
Log.d("Subscribe Thread", Thread.currentThread().toString());
}, error -> {
// do something with error
});