I need to run 2 JOB at a specific interval of 4,8,12,16... second and another one is 5,9,13,17...second.
I have used Interval operator in RxJava. Job B needs to run after Job A. Job B should sleep when Job A is running and vice versa. Till now the code looks below
var compositeDisposable = CompositeDisposable()
compositeDisposable.add(Observable.interval(0, recordIntervalPeriod, TimeUnit.MILLISECONDS)
.serialize()
.subscribe {
JobA()
})
compositeDisposable.add(Observable.interval(0, recorderStopIntervalStartTime, TimeUnit.MILLISECONDS)
.serialize()
.subscribe {
JobB()
})
Need help in following
1. Best way to achieve the above using RxJava
2. Run JobA for 4 second then run JobB for 4 second and repeat the process again.
I would suggest you use a single job that runs every second, and decide each time which job to call based on the counter value:
val disposable = Observable.interval(1, TimeUnit.SECONDS)
.serialize()
.subscribe { counter ->
if (counter % 4 == 0L) {
jobA()
} else if ((counter - 1) % 4 == 0L) {
jobB()
}
}
If you still want to use two observables, I think this will work too:
val disposable = CompositeDisposable()
disposable.addAll(
Observable.interval(4, TimeUnit.SECONDS)
.subscribe {
jobA()
},
Observable.interval(4, TimeUnit.SECONDS)
.delay(1, TimeUnit.SECONDS)
.subscribe {
jobB()
})
Disclaimer: I haven't used RxJava a lot.
What about
Observable.interval(4,TimeUnit.SECONDS)
.flatMap({
jobA().zipWith(Observable.timer(1, TimeUnit.SECONDS) }
.flatMap { jobB() }
}, maxConcurrent = 1).subscribe()
I'm assuming jobA() and jobB() are observables of some sort.
Job A should wait on Job B being done, because of the max concurrency set to 1.
Job B should wait on Job A or 1 second from the start of Job A, whichever happens latest.
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(/* ... */);
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()
}
})
For example we have a call:
getObsevable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onSuccess, ::onError))
Is there is a way to not launch it if condition is true? For example:
getObsevable()
.stop({ Calendar.getInstance().get(Calendar.MINUTE) % 2 == 0 })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onSuccess, ::onError))
In this code I mean we should not launch observable if current minute is odd. And launch it if current minute is even.
Yes, you can use filter to achieve this. So what happens when filter is used, if the condition doesn't meet, the stream below doesn't get executed.
In your case (or example), if the current minute is odd, the subscribe method wont be called. However, if it's even, the stream will work just the way it's written.
getObservable()
.filter({ Calendar.getInstance().get(Calendar.MINUTE) % 2 == 0 })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onSuccess, ::onError))
You can read about it here -> http://reactivex.io/documentation/operators/filter.html
Assign it to a disposable, and dispose it before start another call, or wherever you need to dispose it
Disposable disposable;
public void createDisposableWithMyObservable(){
if(disposable != null) disposable.dispose();
disposable = getObsevable()
.stop({ Calendar.getInstance().get(Calendar.MINUTE) % 2 == 0 })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onSuccess, ::onError));
}
The method I want to test contains of two calls to the retrofit service:
internal fun poll(): Completable {
return presenceService.askForFrequency(true).toObservable()
.flatMap { it -> Observable.interval(it.frequency, TimeUnit.SECONDS, Schedulers.io()) }
.flatMapCompletable { _ -> presenceService.sendHeartbeat() }
.subscribeOn(Schedulers.io())
.retry()
}
The presenceService is injected in the class, so I provide the mocked one for the test:
val frequency = PublishSubject.create<Presence>()
val heartbeat = PublishSubject.create<Unit>()
val mockPresenceService = mock<PresenceService> {
on { askForFrequency(any()) } doReturn frequency
on { sendHeartbeat() } doReturn heartbeat
}
The test, that checks that askForFrequency method is called works correctly, but test that checks that the polling request is sent never works:
#Test
fun presenceService_sentHeartbeat() {
RxJavaPlugins.setIoSchedulerHandler { scheduler }
frequency.onNext(Presence(1)) //polls with 1s interval
heartbeat.onNext(Unit)
presenceMaintainer.onActivityResumed(any())
scheduler.advanceTimeBy(2, TimeUnit.SECONDS)
verify(mockPresenceService).askForFrequency(true) //works correctly
verify(mockPresenceService).sendHeartbeat() //never works
}
The logs from the unit test run are:
Wanted but not invoked:
presenceService.sendHeartbeat();
However, there was exactly 1 interaction with this mock:
presenceService.askForFrequency(true);
The question is: how to test that the second method (sendHeartbeat) is also called (possibly several times)?
Meanwhile I found out that the problem lies in the second flatmap, because the test for this method works correctly (verifies that method was called 60 times):
internal fun pollTest(): Observable<Presence> {
return Observable.interval(1, TimeUnit.SECONDS, Schedulers.io())
.subscribeOn(Schedulers.io())
.flatMap { it -> presenceService.askForFrequency(true).toObservable() }
}
#Test
fun presenceService_sentHeartbeat() {
frequency.onNext(Presence(1))
val result = arrayListOf<Unit>()
presenceMaintainer.pollTest().subscribe({ t -> result.add(Unit) })
Thread.sleep(60*1000)
println(result.size)
verify(mockPresenceService, Times(60)).askForFrequency(true)
}
But when I change the order of the calls to askForFrequency -> map to interval -> map each tick to poll call, test stops working and mock is called only once.
By default, Observable.interval() runs on the computation scheduler, and not the io scheduler. That means, that the 2 second wait will be run in real time, so your test will finish 2 seconds before the call to sendHeartBeat().
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).