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(/* ... */);
Related
Are there any clear and smart solution to dispose observable from its subscribe method?
E.g. I have an Observable.interval that emits Long every second. In my subscribe method i want to check if 20 seconds gone than dismiss subscription.
val observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable.dispose()//I cant call dispose here because variable 'observable' is not avaliable yet
}
})
What is the easiest and right way to achieve this logic?
I found one simple solution. Just divide variable declaration and initialization in to two steps.
E.g.
var observable:Disposable? = null
observable = Observable.interval(1000,TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ num ->
if(num == 20.toLong())
{
observable?.dispose()
}
})
I have a State(Enum) that contains (Good, Non-Critical, Critical) values
So requirement is :
should trigger when state goes in non-critical state.
should trigger when state goes in critical state.
should trigger when state stays in critical state for 15 seconds.
Input :
publishSubject.onNext("Good")
publishSubject.onNext("Critcal")
publishSubject.onNext("Critcal")
publishSubject.onNext("NonCritical")
publishSubject.onNext("Critacal")
publishSubject.onNext("Critical")
publishSubject.onNext("Good")
and so on...
See Code Structure for Reference:
var publishSubject = PublishSubject.create<State>()
publishSubject.onNext(stateObject)
publishSubject
/* Business Logic Required Here ?? */
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
Please help,
Thanks in Advance,
You can use distinctUntilChanged() to suppress events that don't change the state. Filter out the normal events using filter().
Use the switchMap() operator to create a new subscription when the state changes. When the state is "critical", use the interval() operator to wait out the 15 seconds. If the state changes in that 15 seconds, switchMap() will unsubscribe and re-subscribe to a new observable.
publishSubject
.distinctUntilChanged()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter( state -> state != State.Normal )
.switchMap( state -> {
if (state == State.Critical) {
return Observable.interval(0, 15, TimeUnit.SECONDS) // Note 1
.map(v -> State.Critical); // Note 2
}
return Observable.just( State.Noncritical );
})
.subscribe( ... );
interval() is given an initial value of 0, causing it to emit a value immediately. After 15 seconds, the next value will be emitted, and so on.
The map() operator turns the Long emitted by interval() into
The first two parts of your requirements should be combined into one. You're asking for the chain to be triggered on NonCritical and Critical events, ergo the chain should not be triggered for Good event. Likewise, you only need to trigger an event if the state is different from a previous event. For this two .filter events should suffice:
var lastKnownState: State = null
publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter(this::checkStateDiffers) // Check we have a new state
.filter { state -> state != State.Good } // Check event is good
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
...
private fun checkStateDiffers(val state: State): Boolean {
val isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
The timeout requirement is a bit trickier. RxJava's timeout() operator gives the option of emitting an error when nothing new has been received for a period of time. However I am assuming that you want to keep listening for events even after you receive a timeout. Likewise, if we just send another Critical event it'll be dropped by the first filter. So in this case I'd recommend a second disposable that just has the job of listening for this timeout.
Disposable timeoutDisp = publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.timeout(15, TimeUnit.SECONDS)
.onErrorResumeNext(State.Timeout)
.filter { state -> state == State.Timeout }
.filter { state -> lastKnownState == State.Critical }
.subscribe {
AppLogger.printLog("Timeout Success --> ")
}
Also adjust the checkStateDiffers() to not save this Timeout state in the first chain.
private fun checkStateDiffers(val state: State): Boolean {
if (state == State.Timeout) return true
var isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
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 am trying to achieve the following. I load a list of objects I want to get values to put in a list later.
First I gather all the values into an array (to mountain order) using flatmap and then when everything is done I populate an adapter.
The thing I am unable to do is to repeat the operation ever xxx seconds. I understand its done using an interval. Still I get no result at all, or only none repeating one result.
Here’s my code:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance().getApi().getService().getData(currency.getUrl())))
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::success, this::error, this::valuesRetrieved);
recyclerView = ((CurrencyListFragment) controller).getRecyclerView();
LinearLayoutManager linearManager = new LinearLayoutManager(controller.getContext());
recyclerView.setLayoutManager(linearManager);
}
private void valuesRetrieved() {
listAdapter adapter = new listAdapter(valuesFromResponse);
recyclerView.setAdapter(adapter);
}
private void success(Object response) {
valuesFromResponse.add(response);
}
Where do I put
.interval(5, TimeUnit.SECONDS).timeInterval()
Well actually, you do not put interval anywhere, for repeating the operation every x interval, you should use repeat operator variant called repeatWhen where you can provide your interval logic in this way:
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
repeatWhen() will hand to you an Observable that transform your source Observable onCompleted() events as onNext() (with void), you should return Observable that emits onNext() which signals to resubscribe to your source Observable - meaning repeat the operation. While onCompleted()/onError() will be delivered as onCompleted()/onError() on your source Observable.
recommended reading regarding repeatWhen/retryWhen.
One thing to consider, as repeatWhen() will basically swallows all your onCompleted events (as you're repeating the operation there is no onCompleted(), your Observable will not stop from by itself!), then you should gather and update the adapter differently, I guess you can simply use toList() to gather all items to single onNext() (a replacement to your success() logic) and then on each onNext updates the list (what you're doing on onCompleted right now), to sum it up:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance().getApi().getService().getData(currency.getUrl())))
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.toList()
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::valuesRetrieved, this::error);
EDIT:
Your timeout and retry logic are applied to the entire operation chain, so if all the network requests together take more than Constants.TIMEOUT_IN_SECONDS you will get timeout exception, you probably just want to retry and time out each individual request. like this:
Observable.fromIterable(URLList)
.concatMap(url -> standartRequest(App.getInstance()
.getApi().getService().getData(currency.getUrl())
.retry(Constants.RETRY_COUNT)
.timeout(Constants.TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)))
.toList()
.repeatWhen(completed -> completed.delay(5, TimeUnit.SECONDS))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::valuesRetrieved, this::error);
I am repeating my retrofit call every 2 second after it is completed
//Retrofit Builder
val retrofitBuilder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("http://worldtimeapi.org/")
.build()
val timeApi = retrofitBuilder.create(TimeApi::class.java)
val timeObservable = timeApi.getTime()
timeObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.repeatWhen { completed -> completed.delay(2, TimeUnit.SECONDS) }
.subscribe(object : Observer<Time> {
override fun onComplete() {
Log.e("MainActivity", "It is completed")
}
override fun onSubscribe(d: Disposable) {
Log.e("MainActivity", "you have successfully subscribed ")
}
override fun onNext(t: Time) {
progress.visibility = View.INVISIBLE
txtTime.text = t.unixtime
Log.e("MainActivity", "OnNext Called" + t.unixtime)
}
override fun onError(e: Throwable) {
Log.e("MainActivity", "ERROR")
}
})
}
See the Log Cat , onNext is called after every 2 second.
The repeatWhen can do the job, but in my sense, the interval could do the job as well, just like :
Observalbe.interval(5, TimeUnit.SECONDS)
.flatMap( /* Your Observabler.fromItere().concatMap().retry().timeout()*/)
.subscribe{ /* refresh RecyclerView* / }
In this way, you use flatMap to switch one stream (interval) to another stream (your business logic).