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)
}
Related
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.
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 am using Reactive Extensions (RxJava 2) to perform an RPC call to a Bluetooth device, resulting in an incoming data stream, which I subsequently parse, also uxing Rx. The resulting API is a simple Flowable<DownloadedRecord>. For this, I am building on top of the Rx API of the Sweetblue library for Android.
My problem is there is a race condition between 'requesting' the device to start streaming, and subscribing to the stream in time to make sure no packets are missed.
I use a Completable to first perform an RPC call to request data streaming to commence, andThen( readRecords ). A race condition seems to occur where some packets are emitted by Sweetblue, before readRecords had time to subscribe to this stream, thereby 'breaking' readRecords.
To abstract away from this concrete scenario, take the following stand alone code:
val numbers = PublishSubject.create<Int>()
var currentTotal = 0
val sumToTen = numbers
.doOnNext { currentTotal += it }
.doOnNext { println( "Produced $it" ) }
.takeUntil { currentTotal >= 10 }
.doOnComplete { println( "Produced a total of $currentTotal." ) }
Completable.fromAction { numbers.onNext( 9 ) } ) // Mimic race condition.
.andThen( sumToTen )
.subscribe { println( "Observed: $it, Current total: $currentTotal" ) }
numbers.onNext( 1 )
The numbers.onNext( 9 ) call mimics the race condition. This number is never observed by sumToTen, since sumToTen is only subscribed to on the next line. Thus, the stream never completes.
After some investigating, I understand I can 'solve' this problem by using replay and connect.
val numbers = PublishSubject.create<Int>()
var currentTotal = 0
val sumToTen = numbers
.doOnNext { currentTotal += it }
.doOnNext { println( "Produced $it" ) }
.takeUntil { currentTotal >= 10 }
.doOnComplete { println( "Produced a total of $currentTotal." ) }
.replay( 1 ) // Always replay last item upon subscription.
Completable.fromAction { sumToTen.connect() }
.andThen( Completable.fromAction { numbers.onNext( 9 ) } )
.andThen( sumToTen )
.subscribe { println( "Observed: $it, Current total: $currentTotal" ) }
numbers.onNext( 1 )
Now the sumToTen stream completes, since, by first connecting to sumToThen prior to 'starting to stream data' (onNext( 9 )), this stream subscribes to numbers, thus the intended side effects occur (currentTotal). But, '9' is only observed when the replay buffer is big enough (in this case it is). For example, replacing replay( 1 ) with publish will make the stream complete ("Produced a total of 10"), but will not observe '9'.
I am not fully satisfied with this solution for two reasons:
This simply minimizes the chance of the race condition occurring. How large to set the replay buffer is arbitrary.
This will always keep the specified number of elements in replay in memory, even though the intent is only to do so until subscribed to.
Practically speaking neither of these are a real problem, but this is an eye soar from a maintainability perspective: the code does not clearly communicate the intent.
Is there a better way to deal with this scenario? E.g.:
A replay operator which only replays for one subscriber (thus drops the cache once emitted for the first time).
An entirely different approach than what I explored here with publish/connect?
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 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.