Model View Intent arch in Jetpack Compose UI


Why MVI Architecture
There are already MVC, MVP and MVVM for building mobile applications.
Why MVI, what's new in that? Touted as better than MVVM, but how?
Here are a few things recommended about MVI presentational arch pattern.
Supports Unidirectional Data Flow: due to flow of data from data layer, domain layer into UI layer.
Immutability: State, if immutable, helps in reducing bugs, when data is passed around.
Separation of Concerns: Clear separation between UI, state, and logic.
Reactive UI: Works well with reactive programming (e.g., Kotlin Flow, RxJava).
Testability: Reducers(Pure functions) and clear state transitions make testing easier.
Predictable State Management:All updates are handled through the state, making the flow easy to debug.
When to Use:when Complex state management is required involving lot of data Apps with heavy user interaction & those with requiring real-time updates, more significantly when state change is predictable and error free as in case of financial applications.
Ex: ecomm & social media apps including chat screens that frequently change data & needing predictable, error free state updates.
Let's begin:
MVI Architecture is a presentational architecture pattern(MVx) for building complex user interfaces.
Key components involved:
Essential: Intent, Model, State, ViewModel ,Action, Reducer (Simple implementations can use just these)
Optional: Effect, and Middleware.(Advanced use cases might need these)
•Model: The state of the UI at any given time.
•View: Renders the UI based on the current state.
•Intent: Represents user actions (e.g., button clicks).
•ViewModel: Manages the state and business logic.
• Action: Represents actions that trigger state changes.
•Reducer: Pure function that takes the current state and an action to produce a new state.
•Effect: Side effects (e.g., network calls, animations) triggered by actions.
•Middleware: Intercepts actions to perform additional logic (e.g., logging, analytics).
Code for the base viewmodel class
Example of middleware intercepting intents:
Here, middleware is integrated into the BaseViewModel as an open function that can be overridden by subclasses.
Middleware can:
Log intents for debugging or analytics.
Validate or block intents based on conditions.
Modify intents before they are processed.
The processIntent function ensures that intents are passed through middleware before being handled.
This approach makes the BaseViewModel highly flexible and reusable for various use cases.
Let's get into the details:
Let’s see how this works in a ChatViewModel
that handles real-time updates and background tasks.
How Dispatchers Are Used in the Example connectToChat:
Uses runOnBackground to simulate a network call on Dispatchers.IO.
Updates the state and emits effects on the main thread using updateState and emitEffect.
sendMessage:
Simulates sending a message on Dispatchers.IO.
Updates the state on the main thread.
receiveMessage:
Updates the state and emits effects on the main thread.
Benefits of Using Dispatchers for Responsive UI:
Ensures that UI updates and side effects always happen on the main thread.
Efficient Background Tasks:
Offloads heavy operations (e.g., network calls) to background threads.
Thread Safety:
Prevents crashes or undefined behavior caused by updating the UI from a background thread.
Few things to note wrt to why StateFlow is used to represent UI State and not SharedFlow and SharedFlow for Intent, Actions and Events and StateFlow for State Why?
State represents the current UI state (e.g., loading, success, error).
StateFlow is designed for state persistence:
It holds the latest value and replays it to new collectors (e.g., the UI).
Ensures the UI always has the latest state when it (re)subscribes (e.g., after rotation).
Example: If the UI is in a "loading" state, new subscribers (like a recreated Activity) immediately see the loading state.
SharedFlow for Events (Intents, Actions, Effects) Why?
Intents, Actions, and Effects represent events (e.g., button clicks, navigation, toasts).
SharedFlow is designed for event streaming:
Events are short lived and should not be replayed to new collectors.
Avoids duplicate processing (e.g., showing a toast twice after rotation).
Example: A "Show Toast" effect should trigger only once, even if the UI resubscribes.
Key Differences
Why This Matters in MVI StateFlow ensures the UI always reflects the latest state (e.g., after screen rotation).
SharedFlow ensures events are processed exactly once (no duplicates).
The main purpose of this article was to provide with the MVI framework implementation:
**
For code with a demo app, please check
MainActivity.kt, DomainData.kt & app/build.gradle.kts files from my github repo
**
Subscribe to my newsletter
Read articles from Tejaswi B directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Tejaswi B
Tejaswi B
Professional android developer since 7+ years. https://www.linkedin.com/in/tejaswib91/