1

I'm trying to wrap my head around how to implement something in RxJava (2.0). It's for Android and I'm using Kotlin, although the choice of platform and language shouldn't matter here.

The idea is that I'd base some sort of MVP architecture on RxJava. In this implementation I'm thinking about an Activity (could be a Fragment or a custom View as well) exposes a stream of values (Booleans for simplicity) which indicate lifecycle events, or whether the view is attached or detached.

The underlying idea is basically this:

private val lifecycleEvents = PublishSubject.create<Boolean>()
val screenStates: Observable<Boolean> = lifecycleEvents.hide()

override fun onResume() {
    super.onResume()
    lifecycleEvents.onNext(true) // I'm attached!
}

override fun onPause() {
    lifecycleEvents.onNext(false) // I'm detached!
    super.onPause()
}

override fun onDestroy() {
    lifecycleEvents.onComplete() // I'm gone        
    super.onDestroy()
}

And then from the other end, the Presenter exposes an Observable that is a stream of objects representing screen states - to be rendered by the View.

(This follows the concept explained in this series http://hannesdorfmann.com/android/mosby3-mvi-1 - which boils down to the fact that the Presenter feeds the View with standalone objects encapsulating screen states in their entirety rather than multiple different methods on the View).

And then I'd like to bind these two observable streams so that:

  • Whenever the View gets detached, input from the Presenter is disregarded (and it's not buffered, so as not to run into any backpressure problems)

  • However, once the View gets reattached, it picks up the latest state the Presenter emmitted. In other words, only one state instance is to be buffered at most.

It would work as follows (assuming the states are of String type for simplicity):

val merged: Observable<String> = ???

val attached = true
val disattached = false        

screenStates.onNext(attached)
fromPresenter.onNext("state A")
fromPresenter.onNext("state B")

screenStates.onNext(disattached)
fromPresenter.onNext("state C") // this won't survive at the end
fromPresenter.onNext("state D") // this will "override" the previous one.
// as that's the last state from BEFORE the screen is reattached

screenStates.onNext(attached)
// "state D" should be replayed at this point, "state C" is skipped and lost

fromPresenter.onNext("state E")

// what "merged" is supposed to have received at this point:
// "state A", "state B", "state D", "state E"

I'm not sure what the best, idiomatic solution is.

I tried to implement it as an ObservableTransformer, but I couldn't quite get it right. I believe the transformer should be stateless, whereas my solution gravitated towards explicitly keeping track of what was emmitted and buffering the last element "manually" etc., which feels messy and too imperative, so I suppose it's wrong.

I found https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableValve.java, but the implementation looks very complex and I can't believe it couldn't be done in a simpler manner (I don't need all the flexibility, I only want something that works for the described usecase).

Any insights would be appreciated, including whether there's something else I should take into consideration still, within the context of Android. Also note that I don't use RxKotlin bindings (I may, I just didn't suppose they should be required here).

EDIT:

Below is my current implementation. As I said, I'm not too happy about it because it's explicitly stateful, and I believe this should be achieved declaratively, leveraging some constructs of RxJava.

I needed to merge two streams of different types, and because combineLatest nor zip didn't quite do it, I resorted to a trick, creating a common wrapper for both distinct type of events. It introduces certain overhead again.

sealed class Event
class StateEvent(val state: String): Event()
class LifecycleEvent(val attached: Boolean): Event()

class ValveTransformer(val valve: Observable<Boolean>) : ObservableTransformer<String, String> {
    var lastStateEvent: Event? = null
    var lastLifecycleEvent = LifecycleEvent(false)

    private fun buffer(event: StateEvent) {
        lastStateEvent = event
    }

    private fun buffer(event: LifecycleEvent) {
        lastLifecycleEvent = event
    }

    private fun popLastState(): String {
        val bufferedState = (lastStateEvent as StateEvent).state
        lastStateEvent = null
        return bufferedState
    }

    override fun apply(upstream: Observable<String>): ObservableSource<String> = Observable
            .merge(
                    upstream.map(::StateEvent).doOnNext { buffer(it) }, 
                    valve.distinctUntilChanged().map(::LifecycleEvent).doOnNext { buffer (it) })
            .switchMap { when {
                it is LifecycleEvent && it.attached && lastStateEvent != null ->
                    // the screen is attached now, pump the pending state out of the buffer
                    just(popLastState())
                it is StateEvent && lastLifecycleEvent.attached -> just(it.state)
                else -> empty<String>()
            } }
}
Konrad Morawski
  • 8,307
  • 7
  • 53
  • 91

2 Answers2

2

To combine @TpoM6oH's answer with the original proposal:

val bufferedEvent: Observable<Event> = BehaviorSubject.create()
bufferedEventResult = valve.switchMap( 
     viewEvent -> if (viewEvent) 
                       bufferedEvent 
                  else Observable.never() )

The switchMap() operator takes care of subscribing and unsubscribing.

You can then split the resulting observable into the requisite states and events, using publish(). I'm not sure what the need for ObservableTransformer is.

Bob Dalgleish
  • 8,167
  • 4
  • 32
  • 42
  • Thanks Bob! I can't try it at the moment (too busy at work and this is just for my side project), but I'll get down to it. I upvoted your answer and will accept it once I test the solution. – Konrad Morawski Jun 20 '17 at 23:18
  • Honestly, I'm not sure how to fit this code into my `ObservableTransformer`... And why buffer both? I only need to buffer states, not lifecycle events – Konrad Morawski Jun 30 '17 at 21:26
  • Well, OK, I do buffer lifecycle events. Still not sure how it should be included in the {{ObservableTransformer}}. Eg. how and where do I subscribe {{bufferedState}} to the incoming stream (that's subject to the transformation)? How and where would I unsubscribe it afterwards? – Konrad Morawski Jul 01 '17 at 01:21
  • There are two separate switches in my example, as I didn't understand that you need them combined. I will edit my example. – Bob Dalgleish Jul 01 '17 at 18:11
  • Thanks. The idea and the purpose of the transformer is to solve the lifecycle problem in MVP / Android. The view can be attached and reattached (on Android the same screen is actually reinitialized eg. when the device is rotated) and Presenter should withhold feeding the view with "screen states" until it's reattached. It's often done by implementing lifecycle methods on the Presenter itself and subscribing/ unsubscribing the View when it happens. I'd like the View to emit a stream of (say) bools instead, serving as a "valve", and have this solved on a single stream level without unsubscribing – Konrad Morawski Jul 02 '17 at 11:26
1

It seems to me that you are looking for a BehaviorSubject - that's a subject that emits the most recent item it has observed and all subsequent observed items to each subscribed observer.

If you use it in the presenter, unsubscribe from it when view is detached and subscribe to it when view is attached you should get what you want.

TpoM6oH
  • 8,385
  • 3
  • 40
  • 72
  • Thanks for your reply. I'm aware of `BehaviorSubject` and the possibility of implementing it that way. But I was wondering if it was achievable *without* unsubscribing and resubscribing. I mean, I know we have to unsubscribe eventually, but I'd like to do it only once, when the view gets destroyed - not every time it's paused. – Konrad Morawski Jun 18 '17 at 19:03
  • 1
    Seems tricky to do it completely without unsubscribing, but you can move that logic to the presenter, having a behaviour subject for all your view events and another subject that will be exposed to the view, unsubscribing the exposed subject from the behaviour subject when you get `detach` event and subscribing when you get `attached` request. Then you can move that to base class of your presenter and it will look pretty good. – TpoM6oH Jun 18 '17 at 19:20
  • that's possible as well I guess. In any case, I edited the question pasting my current implementation - it's as good as I managed to come up with. Obviously Kotlin's expressiveness comes in handy here - the ugliness of equivalent Java implementation would be much more apparent. – Konrad Morawski Jun 18 '17 at 20:32