I am using rxjava3 observable in foreground service to sync data after every 3 minutes but this observer miss sometime when phone in doze mode
disposable = Observable.interval(1, 3 * 60 * 1000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io()).observeOn(
AndroidSchedulers.mainThread()
).subscribe {
Log.d(TAG, "Observe Called")
appendLog("Observe Called on ${formatDate(System.currentTimeMillis().toString())}")
}
Related
What I want to achieve?
I have a task to download images but as the screen scrolls it will cancel previous downloads and start downloading new ones. I want that when it cancel the coroutine downloading previous image it stop instantly and free up the bandwidth so new images download faster.
What I have tried?
I have tried multiple ways to stop the coroutine but it keeps going until it finishes the downloading even after cancelling the coroutine. When I cancel the coroutine it makes a variable isActive to false and stop calling further suspended function. But the problem is if its running a loop for 1000000 times or downloading an image from network this task will not canceled unless completed. Like loop will complete it's 1000000 iterations then the coroutine will be cancelled.
I have tried these but no success:
job.cancel()
scope.cancel()
I have tried so many ways for achieving this but got no solution. I can't use any library right now in my project.
This use case not achieved by Threads, Executor Service, Coroutines. Because all behave the same.
More questions same like this :
How do I cancel a kotlin coroutine for a blocking download operation
AsyncTask is not cancelling the long running operation in android
Leaking Service held by Coroutine
A kotlin coroutine must cooperate to allow cancellation. That means it has some check points calling one suspend function. This makes sense as some procedures are atomic and should not be stopped in the middle.
One example of bad coroutine that can not be cancelled:
var job = launch {
var time = System.currentTimeMillis()
var i = 0
while (i < 1000) {
if (System.currentTimeMillis() >= time) {
println("Loop number ${++i} ")
time += 500
}
}
}
To make it cancellable, you can add yield() at the begining of each iteration. Following is a cancellable coroutine:
coroutineScope {
var job = launch {
var time = System.currentTimeMillis()
var i = 0
while (i<1000) {
yield()
if (System.currentTimeMillis() >= time) {
println("Loop number ${++i}")
time += 500
}
}
}
// wait some time
delay(1300)
println("Stopping the coroutine....")
job.cancel()
job.join()
// or call job.cancelAndJoin()
}
Coroutine cancellation is cooperative. A coroutine code has to
cooperate to be cancellable. All the suspending functions in
kotlinx.coroutines are cancellable. They check for cancellation of
coroutine and throw CancellationException when cancelled. However, if
a coroutine is working in a computation and does not check for
cancellation, then it cannot be cancelled, like the following example
shows:
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
Run it to see that it continues to print "I'm sleeping" even after cancellation until the job completes by itself after five iterations
Making computation code cancellable
like the following example shows:
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
Refer to the official docs here
Try to cancel the Context:
this.coroutineContext.cancel()
Hi I have a rxJava observable and Flatmap which I want to convert to kotlin coroutine Flow.
rxJava observable
val startFuellingObservable: Observable<Void>
subscription / flatmap
subscriptions += view.startFuellingObservable
.onBackpressureLatest()
.doOnNext { view.showLoader(false) }
.flatMap {
if (!hasOpenInopIncidents()) {
//THIS API CALL RETURNS RX OBSERVABLE
startFuellingUseCase.execute(equipmentProvider.get())
} else {
val incidentOpenResponse = GenericResponse(false)
incidentOpenResponse.error = OPEN_INCIDENTS
Observable.just(incidentOpenResponse)
}
}
.subscribe(
{ handleStartFuellingClicked(view, it) },
{ onStartFuellingError(view) }
)
I have changed my observable to Flow
val startFuellingObservable: Flow<Void>
as it is now Flow
I am able to do this
view.startFuellingObservable
.onEach { view.showLoader(false) }
*** I have made the API call to return Flow instead of observable
But I am not sure how to do the rest of the flatmap using Flow
Could you please suggest how to do the same code using Flow please
Thanks
R
late answer but I hope it may help others.
First of all, there is a Flow from kotlin Concurrent so you definitely need to import
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
which belongs to import kotlinx.coroutines.flow
Observables<T> from RxJava will be Flow<T>
Rxjava FlatMap is FlatMapMerge in Kotlin Flow API
FlatMapMerge example:
val startTime = System.currentTimeMillis() // remember the start time
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms
.flatMapMerge { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
result:
1: First at 136 ms from start
2: First at 231 ms from start
3: First at 333 ms from start
1: Second at 639 ms from start
2: Second at 732 ms from start
3: Second at 833 ms from start
there are 3 types of FlatMap in Flow API
FlatMapConcat
This operator is sequential and paired. Once the outerFlow emits once, the innerFlow must emit once before the final result is collected. Once either flow emits a Nth time, the other flow must emit a Nth time before the Nth flatMapResult is collected.
FlatMapMerge
This operator has the least restrictions on emissions, but can result in too many emissions. Every time the outerFlow emits a value, each of the innerFlow emissions are flatMapped from that value into the final flatMapResult to be collected. The final emission count is a multiplication of innerFlow and outerFlow emissions.
FlatMapLatest
This operator cares only about the latest emitted results and does not process old emissions. Every time the outerFlow emits a value, it is flatMapped with the latest innerFlow value. Every time the innerFlow emits a value, it is flatMapped with the latest outerFlow value. Thus the final emission count is a value between zero and innerFlow emissions times outerFlow emissions.
I have a heart-rate sensor that emits a value periodically (anywhere between 500-3000 milli). When the heart rate sensor emits is non-deterministic. With RXJava, i would like to have a constant emitting the 'last seen' heart rate value and the constant emits the value for up to 10 Seconds until it marks it as too-stale & sends a NULL instead. NULL denotes that the heart rate sensor is no longer emitting sensor readings.
I have the following (kotlin) code:
val heartRateObservable: Observable<BLEDataValue> = observable
.flatMap { it.setupNotification(characteristic.uniqueIdentifier) }
.flatMap { it }
.map { BTDataPacket(characteristic.uniqueIdentifier, BleParseable(it)).btValue() }.onErrorReturn { BLEDataValueHeartRate(null) }
return Observable.combineLatest(Observable.interval(1000, TimeUnit.MILLISECONDS), heartRateObservable, BiFunction { _, t2 -> t2 })
Question: Is it possible to introduce a way to replay the last seen heart-rate value up to when the last value becomes stale (i.e. after not seeing any heart-rate readings for 10 seconds).. when a heart rate value is seen it replay this until a new heart-rate value arrives OR the timeout of 10 seconds passes as the last value is now too-stale?
You can use either takeWhile or takeUntil operators to complete your rate observable.
wait for 10 emissions :
Observable heartbeat = Observable.interval(1000, TimeUnit.MILLISECONDS)
.takeWhile(e -> e < 10)
or by using a timer witj takeUntil
Observable heartbeat = Observable.interval(1000, TimeUnit.MILLISECONDS)
.takeUntil(Observable.timer(10000, TimeUnit.MILLISECONDS))
After that you said :
... and emit this for up to 10,000 Milliseconds from the last received sensor value after ...
For that you can use switchMap (If I've understood your question)
heartRateObservable
.switchMap(data -> heartbeat.map(data))
Hence the heartbeat starts emitting values after each last emitted data.
val heartRateObservable: Observable<BLEDataValue> = observable
.flatMap { it.setupNotification(characteristic.uniqueIdentifier) }
.flatMap { it }
.map { BTDataPacket(characteristic.uniqueIdentifier, BleParseable(it)).btValue() }.onErrorReturn { BLEDataValueHeartRate(null) }
return heartRateObservable
.switchMap { data -> Observable.interval(1000, TimeUnit.MILLISECONDS)
.takeWhile(e -> e < 10)
.map(l -> data)
}
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.
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().