MVI implementation in Kotlin Multiplatform using the Reaktive library (Part 1)
This Part one contain the basic of MVI pros and cons and also why i choose MVI for KMM.
Part Two will explore more about MVI implementation on KMM, also few library that support on it such as Ktor, Kotlin-Serialization, Jetpack Compose and Swift UI.
MVI actually inspire with Redux from react, what is redux?
Redux is a pattern and library for managing and updating application state, using events called “actions”. It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion. This is a very similar pattern to MVVM, with some important differences. In many ways it’s a merging of MVVM & Redux.
The purpose of MVI is managing and updating application state using event that called actions, with rules ensure that the state can be only be updated in predictable. Undirectional and cyclic data flow.
MVI or you can call Model View Intent, as you can see from above that onlly contain and circulate two component view and model, the data flow only come from one direction. The State come from Model and then Intent come from View and enter model for processing. Those circulation is Called Undirectional DataFlow
Model, A Model represent as a ViewState, contain all necesssary to render the view. In some part Model represented by entity called store. a name that borrowed from redux, but its not exactly name, some developer on MVICore name Model as Feature.
View, observe user an system event, as a result it set intentional triggered action and it listen and react in state of model.
Intent, Intentional to do somethings, Represent future action that changes the state of model.
Actor, a function that takes a current State and an Intent and returns a stream of Effects (results).
Reduce, a function that accepts a current State and an Effect (result) from Actor and returns a new State.
Store, Represent model from MVI, should accept intent and provide stream state.
View. It should accept (render) Models and provide a stream of Events. Here is the interface:
Why no MVP?
The defining feature of MVP is that data is communicated back to the view imperatively. Strangely enough this is likely impossible to implement in Compose. Let’s take a look why.
To imperatively update the UI, presenters rely on having a reference to the view, which is usually an activity or fragment injected into the presenter as an interface. The presenter can call functions on the interface to cause changes to the UI. But a quick glance at a composable shows there’s no return value — so no view references to pass to our presenter, because compose doesnt return anything.
There is also potentially the option of using CompositionLocals in some way to make MVP work, although it seems a bit of an abuse, and if you’re imperatively setting data to trigger re-compositions you may as well cut out the middle man and use MVVM.
So it’s probably safe to say that if you’re excited about integrating Compose into your MVP-based-app the place to start is probably in migrating away from MVP. Which is also suggested within the Compose docs:
Unidirectional Data Flow (UDF) architecture patterns work seamlessly with Compose. If the app uses other types of architecture patterns instead, like Model View Presenter (MVP), we recommend you migrate that part of the UI to UDF before or whilst adopting Compose.
Where MVVM differs from MVP is in how data is communicated back to the view. Rather than imperatively calling the view, in MVVM changes are described by data which are then observable by the view. The UI can then reactively re-compose itself based on the new data.
Const MVI trade off.
- more boiler plate. More code to write.
Pros MVI trade off,
- MVI is a reactive model, passing in parameters through a unique entry, and receiving results and completion responses from a unique exit.
- Large amount application state in different many places in app.
- The App state is update frequently. not agreee
- The logic of update may be complex.
- The app is huge and large size code base that handle and worked by many people
- The state, the source of truth that drives our app;
- The view, a declarative description of the UI based on the current state
- The actions, the events that occur in the app based on user input, and trigger updates in the state
How Migrate from MVVM/MVP
- Define States based on view and activity changes
- for MVP pattern replace all contract view such as view.onSuccess() into view.render(state)
- for MVVM replace all LiveData<Type> into LiveData<State>
- Define state machine with clear API with input and output.
- Define Intent that trigger input from State Machine.
- Trigger intent from View layer
Shared Module
- We managed to put all the code (except UI) into the shared multiplatform module. All the logic plus wirings and conversions between the logic and the UI are shared.
- The implementation of the UI will be very simple: you only need to render the incoming Models and produce Events
- Module integration is also simple. All that is needed is to:
- implement the KittenView interface (protocol)
- instantiate the KittenComponent
- call its life cycle methods at the right time. - This approach avoids the leakage of Rx (or coroutines) into the platforms. This means that we don’t have to manage any subscriptions at the application level
An action is a plain JavaScript object that has a type
field. You can think of an action as an event that describes something that happened in the application.
KMM
We believed in the KMP approach: to share logic without mandating an entire architecture. We hoped that sharing logical components between mobile apps would reduce the development time, improve the quality of the code, and have no impact on performance.
Kotlin Multiplatform Pros:
- Faster Development using single sources. For a new feature, one developer (e.g., an iOS developer) would add model objects, networking, business logic, and unit tests to the multiplatform module. Then the other (e.g. an Android developer) would just plug the feature in. This helped us cut the development time exponentially as we were developing features.
- Improved Code Quality. inherent modularity and testability of the multiplatform code we wrote.
- Maintainable Code.
Kotlin Multiplatform Cons:
- Longer Project Setup. Git repository management, Jira tickets, CI/CD, etc. We came up with several approaches before finally finding the one that worked well for us.
- Requires a Cross-Functional Team, Kotlin/Multiplatform worked well for us because our iOS development team is enthusiastic about learning Kotlin. If this were not the case, the development of almost all of the common code would have fallen on the Android developers and left iOS developers waiting for the business logic to be ready. The cross-functionality of our developers paved the way for multiplatform development.
- New level of iOS Debugging, In particular, the error logs were usually generic and unclear. Sometimes, this slowed us down measurably.
- Multiplatform projects are currently in alpha. Language features and tooling may change in future Kotlin versions.