Hmm I guess the trick here is that it is possible to create the functional loop with:
.scan(StatisticsViewState.idle(), reducer)
.distinctUntilChanged()
.replay(1)
.autoConnect(0)
Also, Redux on its own defined the Reducer as (State, Action) -> State
. In this MVI variant, you have a few mappings prior: Intent -> Action
, then Processor does Action -> Result
, and only then do you actually evaluate the new state with the reducer (based on the result of the task executed by the action processor): (State, Result) -> State
.
So, MVI addresses certain pain points of initial Redux proposition (which, as you can see, would NOT be able to support asynchronous operations nor one-off events — and why MVI added some extra elements in the loop for that).
One could argue that the Processor is a middleware that accesses things only before, but not after evaluation of new state.
They do have some tricks up their sleeves though: one-off actions are portrayed as two results:
.flatMap { tasks ->
// Emit two events to allow the UI notification to be hidden
// after some delay
pairWithDelay(
TasksResult.ActivateTaskResult.Success(tasks),
TasksResult.ActivateTaskResult.HideUiNotification)
}
In the end, all the real magic happens in the ProcessorHolder
s, and from Rx perspective, it uses ObservableTransformer
just like as specified in Jake Wharton’s Managing State with RxJava. A bit boilerplate-y, but theoretically sound.
As long as you can read Rx, there definitely is value in MVI ~ explicit state machine that handles threading / async ops. If you can test Rx transformers, you can test your logic.
Overall, if this example separated State from Data, and instead of always starting the stream with StatisticsViewState.idle()
, they persisted the State
into Bundle in onSaveInstanceState()
and restored that as initial value for the reducer across process death, then this example would be complete.