MVI: Déjà vu as an ex-Web Developer
When I was a web developer, I relied heavily on Redux to manage state across my React projects. It significantly helped me maintain various states and made testing much easier to implement. Before diving into the Model-View-Intent (MVI) pattern and its relation to the Redux approach, let’s first understand what Redux is and its fundamental concepts.
What Is Redux?
Redux is a predictable state container for JavaScript applications. But what does “predictable state” actually mean? Simply , it means that for any given input and the current state of the application, the outcome will always be the same. In other words, given the same initial state and actions, the final state will consistently be identical.
This predictability helps avoid unexpected results and makes debugging much simpler for developers. Additionally, Redux offers benefits like testability and time-travel debugging (though we’ll save that discussion for another time).
How Does Redux Ensure Predictable State?
Redux achieves predictability through the use of pure functions. A pure function guarantees that for the same input (state and action), the output (next state) will always be the same. Moreover, Redux employs the concept of Unidirectional Data Flow: data flows in one direction — from the state to the UI. This unidirectional flow makes it easier to track and predict state changes.
Core Concepts of Redux
Now that we’ve covered what Redux is, let’s briefly explore its core concepts:
1. State: A single source of truth representing the entire application’s state.
2. Action: An object that describes an event or user interaction. Actions are dispatched to modify the state.
3. Reducer: A pure function that takes the current state and an action, then returns a new state.
4. Store: The centralized container that holds the application’s state.
5. Dispatch: A function that sends an action to the store to trigger a state change.
TLDR: In other words, when a user clicks the increment button, they dispatch an action to the reducer, which returns the new state and updates the UI
In this flow, the unidirectional data movement is clear: User Interaction ➔ Action Dispatch ➔ Reducer ➔ New State ➔ UI Update
Since this is something I’ve done a lot, let’s explore how this relates to Android development, specifically with an architecture like MVI (Model-View-Intent). To understand the connection, let’s first dive into what MVI is.
What is MVI ?
Model-View-Intent (MVI) is an architectural pattern used in Android development to manage the flow of data and user interactions within an application. MVI is designed to provide a unidirectional data flow and predictable state management — much like Redux does in React applications.
Concepts of MVI :
MVI consists of three main components:
- Model: Represents the immutable state of the application at any given time. It’s a single source of truth that the UI observes and renders.
- View: The UI layer that displays the state to the user and emits user interactions as intents.
- Intent: Represents the user’s actions or events from the View. Intents are dispatched to modify the state.
How MVI works ?
The MVI pattern enforces a unidirectional data flow similar to Redux:
- User Interaction: The user interacts with the View (clicks a button).
- Intent Emission: The View emits an Intent representing the user’s action.
- Model Update: An Intent Processor (often a ViewModel) handles the Intent and produces a new Model state.
- View Rendering: The View observes changes to the Model and updates the UI accordingly.
This flow ensures that for any given Intent and current Model state, the outcome (next Model state) is predictable.
As software enginner, i need to see the code to fully understand ( a famous example found in internet )
Model : ( State in redux)
it’s state in redux
Intent: ( Actions in Redux )
it’s actions in redux
Model View ( Store + Reducer in Redux ):
the view model is like the store which contains here the reducer ( processIntent )
View Activity / Fragment : ( UI and dispatch )
viewModel.processIntent() it’s like dispatcher
TLDR:
When the user clicks the Increment button, an Intent (
CounterIntent.Increment
) is dispatched. The ViewModel processes the Intent and updates the Model (CounterState
). The View observes the new state and updates the UI (countTextView
) accordingly.
To summarize :
User Interaction:
- Redux: A user clicks a button, and an Action is dispatched.
- MVI: A user taps a button, and an Intent is emitted.
Processing the Event:
- Redux: The Reducer processes the Action along with the current State to produce a new State.
- MVI: The Intent Handler (often within the ViewModel) processes the Intent and updates the Model.
State Update:
- Redux: The Store holds the new State and notifies subscribed components.
- MVI: The ViewModel updates the Model, and the View observes these changes.
UI Rendering:
- Redux: React components re-render based on the new State.
- MVI: The View updates its UI to reflect the new Model state.