from https://lambda-it.ch/blog/post/reactive-data-flow-in-angular-2
I’ve always been curious about MVI, ever since I saw the example implementation of the “Tennis Kata” using RxSealedUnions. To be honest, I’m still not entirely sure how that one works (I think it’d deserve its own article) — it looks nicer with lambdas, and it is an implementation of a finite-state state machine with strictly defined states, where the unions define the given state, and how they react in that given state.
With that union setup, invalid states are impossible.
While this is what MVI hopes to achieve with its immutable view state — to me, and maybe many others, the concept of MVI seems hard to grasp, hard to master, and hard to implement.
If you want to learn about MVI, you’ll see intimidating code and intimidating frameworks such as Cycle.js (JS), Redux (JS), Cyklic (Kotlin), and last but not least Elm Architecture written in Elm (a functional language, unlike C#, C++ or Java — which are imperative)!
And on top of that, many Redux examples love showing synchronous code where you add a TODO to a memory store with no persistence and no asynchronous operations — well, real life isn’t so easy.
But enough talk, I promised MVI, so here we go!
Luckily, Zak Taccardi (he’s a cool guy, you should follow him) put together a fairly approachable example that is based on MVI architecture (and this is pretty much a follow-up to his previous post on State Renderers).
Picture of the Deck of Cards app
The source code is available at Zak Taccardi’s Github repository (ZakTaccardi/deck-of-cards
), and it is written in Kotlin.
However, if you feel uneasy with following Kotlin, then luckily, I took the time and converted the code to Java, which can be found under my name in a fork ( [zhuinden/deck-of-cards](https://github.com/Zhuinden/deck-of-cards)
).
.
.
.
The app itself looks fairly simple — you can tap the card to have the top of the deck dealt, you can ask the app to shuffle the deck. and you can create a new deck (which takes all dealt cards and reshuffles it).
The operations themselves are not immediate — it simulates “loading” in-between, as the “network requests” take time.
Occasionally, there is also “error” injected with a random chance, to show that sometimes, things don’t work out, and we need to handle error states as well.
Components and their interactions with one another
There are a few important things to note here.
Change
— which is used to reduce (evaluate) the new state based on the previous state and the change.render(State)
call from the Presenter to the StateRenderer
.State
into parts and observes changes in it individually (with a distinctUntilChanged()
filter, using a PublishRelay
), then calls the UiActions
which modify the UI when a change occurs in a given property.The last remaining component isn’t strictly part of the architecture, it is an application-specific component: the Dealer.
In fact, I didn’t list it above because it behaves a bit… oddly. Instead of using flatMap()
or concatMap()
to connect the “input” requests with the “output” modified deck or operations — a combination of doOnNext()
is used with individual subscriptions to BehaviorRelay
s , but as the deck
is a BehaviorRelay
, it stores previous value!
If this were strict MVI, the only deck
would exist inside the State
, and the dealer would receive the state, and use the deck from there.
The anomaly of using doOnNext()
with relays most likely stems from that each exposed operation provides multiple events, typically both a change in deck, and a __Operation
. This could be replaced with a common sealed class, and exposing an Observable
that emits multiple events (then calls onComplete()
).
But while the communication with the Dealer isn’t entirely stateless, we can still learn a lot from this example.
All user actions are exposed via PublishRelay
s. The presenter listens to them, and maps them to changes, additionally triggering the Dealer
to do its thing.
The presenter is subscribed for events from both the Intentions
(user actions), and the Dealer
. These are all converted to subclasses of the Change
object, based on which a new state is evaluated from the previous state, and the new state is rendered to the UI.
Naming the actions that can change the state is the heart of all MVI solutions. In Redux, this is the Action
, in this example, it is the Change
. It represents the ways that the state can change.
The state stores the current state (unsurprisingly), but also describes how to evaluate the new state using a Change
. In Redux, this would be called the StateReducer
.
When the object changes, a copy of it is returned, with the given variable modified based on how it is affected by the Change
.
The presenter tells the Ui to render the state, but this is actually handled by the StateRenderer
, which will call the right UiActions
to determine how to make the Ui show what we want it to show.
As we can see, the UiActions represent the View
in MVI, not much more to say about it.
The deck-of-cards example showed us a glance at MVI. Our most important take-aways are:
Intentions
, where the name of the architecture comes from)StateRenderer
PublishRelay
is pretty useful for cutting our stream into multiple streams! :)For more resources, you can check out:
Special thanks again to Zak Taccardi for writing an example that can be understood once looking through it properly.
Source code is available in Kotlin by Zak Taccardi or in Java transcripted by me.