DEV Community

Cover image for Model View Intent arch in Jetpack Compose UI
btejaswi91
btejaswi91

Posted on

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 MVI, but how?

Here are a few things recommended about MVI presentational arch pattern.

Supports Unidirectional Data Flow: As recommended by google. 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.

Image description

Image description

Example of middleware intercepting intents:

Image description

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:

Image description

Image description

Image description

Let’s see how this works in a ChatViewModel that handles real-time updates and background tasks.

Image description

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
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.

Image description

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 ephemeral 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.

Image description

Image description

Key Differences

Image description

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 that works, please check MainActivity.kt, DomainData.kt & app/build.gradle.kts files from my github repo

**

Top comments (0)