0

I have a State(Enum) that contains (Good, Non-Critical, Critical) values

So requirement is :

  1. should trigger when state goes in non-critical state.
  2. should trigger when state goes in critical state.
  3. 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,

Bhuvnesh
  • 1,055
  • 9
  • 29
  • 1
    can you please give some inputs? – Mitesh Machhoya Dec 13 '18 at 12:28
  • Inputs are like these random values (Good, Non-Critical, Critical), coming at every second. and we are passing these value using publishSubject.onNext(random values of state at every sencond). – Bhuvnesh Dec 13 '18 at 12:32
  • @Michael publishSubject.onNext("Good") publishSubject.onNext("Critcal") publishSubject.onNext("Critcal") publishSubject.onNext("NonCritical") publishSubject.onNext("Critacal") publishSubject.onNext("Critical") publishSubject.onNext("Good") See values like that. – Bhuvnesh Dec 13 '18 at 12:43
  • @MichaelDodd, Done please check – Bhuvnesh Dec 13 '18 at 12:52
  • okay, for now I am changing 15 mins to 15 sec (3) - if the Critical state is same for 15 sec then it should trigger. And do you only want (1) and (2) to trigger if the state is different from the last known state? - Yes – Bhuvnesh Dec 13 '18 at 13:00

2 Answers2

2

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( ... );
  1. 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.
  2. The map() operator turns the Long emitted by interval() into
Bob Dalgleish
  • 8,167
  • 4
  • 32
  • 42
  • In all honesty a much better answer than mine. TIL about `distinctUntilChanged()` and `switchMap()` – Michael Dodd Dec 13 '18 at 14:51
  • 1
    :) Learning to use `switchMap()` was a major breakthrough in my understanding of RxJava. Its ability to manage state, subscriptions and sequencing is very powerful. – Bob Dalgleish Dec 13 '18 at 14:56
  • Though will this also fire off the contents of `subscribe` twice? i.e. Once on state change to `Critical` then another 15 seconds later? – Michael Dodd Dec 13 '18 at 14:56
  • Requirement (2) says to fire when it goes into critical state, and (3) says to fire when it stays in critical state for 15 seconds. There are several ambiguities in the requirements, so YMMV. – Bob Dalgleish Dec 13 '18 at 14:58
  • @BobDalgleish, Very nice.., Perfect fit of my requirement. Thanks – Bhuvnesh Dec 14 '18 at 05:43
0

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
}
Michael Dodd
  • 10,102
  • 12
  • 51
  • 64