Make MVI your strength, not your weakness.

While 49% of devs hate MVVM and 49% hate MVI,

there are 2% who leverage it:

  • They understand the reasoning behind each line of code

  • They build architecture that survives any change without a refactor

  • They automate 90%+ of their work with AI

  • They constantly land promotions

After 8 years as an internal system, Production-Ready MVI opens today.

9 limited founding spots, until August 23

Brief

The what: A quick snapshot of the context

There's one part of Android development that still confuses beginners and pisses off every senior dev - the presentation layer architecture.

Everybody argues about which architecture State, Intent, Event, Reducer, Effects, etc. belong to.

And everyone tries to protect their camp.

Devs who choose MVVM and hate MVI:

Devs who prefer MVI over MVVM:

Obviously, there are some devs so confused they can't even pick a side.

They either hate everything:

Or they're OK with any choice:

But the most interesting thing about all four types of developers comes when you take a look at their code.

In 2026, all of them came up with an implementation similar to this:

Same code. They just name it differently.

🐞 The Issue: No one understands the reasoning behind MVP, MVVM and MVI

Breakdown

The when: Reverse engineering the cause and the cost

Take a few seconds and think:

"What does every architecture article in 2026 discuss?"

(spoiler) The code, the abstractions, the syntax.

But what they don't mention at all → the reasoning behind it.

You can't make an architectural decision without understanding what problem it solves.

You learned the pattern, but nobody taught you the decisions.

You'd say, wait:

  • State - provides a single source of truth for screen updates

  • Intent - transforms user actions into State updates by executing business logic

  • Reducer - makes State updates predictable

And you'd be right. But that's not the architecture - it's a component of it, with a specific role.

What a component solves on its own is not the issue the architecture fixes. It's the reason that component exists.

→ To understand the fight, you need to see what each camp was escaping from.

2014. The God Activity era.

Before any architecture, there was one class - the Activity.

It rendered the UI. Held the click listeners. Fired the network calls. Parsed the responses.

3,000 lines in a single file. GOD objects.

Testing it? Impossible. The Activity is a framework class - you can't even instantiate it in a unit test.

MVP appeared to break this monolith into 3 components:

  • Model - the data came from business logic

  • View - the Activity (later Fragment), stripped down to rendering. Dumb by contract.

  • Presenter - a plain class in the middle. Pulls data from the Model, tells the View what to show.

The Presenter has zero Android dependencies. For the first time, you could unit test your screen logic on the JVM.

MVP decoupled logic from the View and made it testable.

Where MVP broke

The Presenter holds a direct reference to the View.

The user rotates the screen. The Activity dies. The Presenter doesn't - and now it's calling methods on a destroyed View.

Memory leaks. Crashes. attachView() / detachView() boilerplate on every screen.

But the deeper problem: MVP has no state.

The Presenter drives the View through imperative commands - showLoading(), hideLoading(), showUsers(), showError(), etc.

The "state" of your screen is a sequence of method calls. Miss one - and the UI desyncs.

After rotation, there's nothing to restore. You replay every command manually or stare at a blank screen.

Developers didn't leave MVP for elegance. They left because the Presenter couldn't survive a configurations changes and process death. And holds a reference to the View

2017. The MVVM era.

Google just shipped Architecture Components: a ViewModel that survives rotation and holds no reference to the View.

The direction of control flips:

  • Model - the data came from business logic, unchanged

  • View - the Activity or Fragment. Subscribes and renders.

  • ViewModel - holds screen data in LiveData. Doesn't know the View exists.

The Presenter told the View what to do. The ViewModel tells no one anything - the View observes it.

Rotation kills the Activity, a new one subscribes, the data is still there.

No memory leaks. No crushes. No replaying commands.

MVVM didn't just fix the leaks. It inverted the dependency that caused them.

Where MVVM broke

1) MVVM never said how many LiveData fields a screen can have.

So every view got its own stream: isLoading, emailError, passwordError, isButtonEnabled.

One LiveData per field. Later - one Flow per field.

And everyone loved it. Because of DataBinding.

You bind each LiveData straight to its view in the XML. Zero code in the Fragment. Nothing to write, nothing to support.

Until QA files a bug: "spinner and error shown at the same time".

2) Nothing defines the update order. Any method mutates any field from any coroutine.

The screen state exists only in the observer's head - as a combination of streams.

You can't reproduce it. And testing 5 independent streams is a nightmare.

More streams. More combinations. More race conditions.

MVVM gave State a home. But it never controlled who changes it, when, and in what order.

→ Developers left “classic“ MVVM because it never provided unidirectional data flow. And Compose demanded one.

3) ⚠️ I just want to make it clear. Just having a ViewModel - that's MVVM. Not more than that.

  • Inverts dependencies between UI and business logic -

  • Survives lifecycle changes changes -

  • Has any type of observable data -

Anything above that - you're in MV(Whatever) territory. You probably take MVI concepts, but don't solve MVI problems - just creating boilerplate abstractions.

2021 → now. The MV(Whatever) era.

Jetpack Compose ships. A composable is a function of state - data in, UI out.

Now try passing 5 LiveData fields into a composable tree. 5 parameters drilled through every layer. Plus a lambda for every click - a screen with 10 interactions means 10 callbacks threaded down the tree.

So developers collapse it:

  • One immutable UiState data class - the whole screen in a single object

  • One onEvent(event) callback going up - instead of 10 lambdas

State flows down. Events flow up.

The class is still called a ViewModel. But look at what it holds. A single State. A sealed hierarchy of user actions. A when that maps them to state updates.

That's State. That's Intent. That's kind of reducer.

One camp calls this MVVM. The other calls it MVI.

→ MV(Whatever) collapsed multiple streams into one State. And all callbacks into one event flow.

Where MV(Whatever) broke

The typical "modern" MVVM or MVI guide you read online builds the architecture like this:

Creates an observable screen State:

class LoginViewModel : ViewModel() {

    private val uiState = MutableStateFlow(ScreenUiState())
}

And reduces Intents into State updates:

class LoginViewModel : ViewModel() {

    private val uiState = MutableStateFlow(ScreenUiState())

    fun mapIntents(intent: Intent) = when(intent){
        is EmailChanged -> uiState.update { it.copy(email = intent.email) }
        is LoginClicked -> login()
    }
    
    private fun login() {
        viewModelScope.launch { 
            uiState.update { it.copy(isLoading = true) }
            //... some logic ...
            uiState.update { it.copy(isLoading = false) }
        }
    }
}

You process Intents with loose viewModelScope.launch calls. No queue, no ordering, the latest update wins.

You update State by just calling state.copy(). No synchronization. Atomic writes, but the State still races.

I won't take apart the other aspects of these abstractions - it makes no sense if the main issue stays unsolved. They just generate boilerplate.

And it will stay unsolved - because there's no Jetpack Compose or Coroutines API that makes it work out of the box.

You could try the libs: Mavericks, Orbit MVI, MVIKotlin. All of them end up solving different problems.

→ You have the MVI attributes, but you don’t solve the issue MVI is supposed to solve: asynchronous work processing with synchronous State updates.

The Fix: Don't learn abstractions. Learn the reasoning behind them

Guideline

The how: Implementation guideline and recommendations

This week I don't have implementation steps. Because the main problem was the reasoning behind:

  • MVP - decouples logic from the View

  • MVVM - inverts dependencies between UI and business logic. Preserves State during configuration changes.

  • MV(Whatever) - adapts MVVM to Composables' UDF architecture

  • MVI - processes asynchronous work with synchronous State updates

→ MVP issue - solved
→ MVVM issue - solved
→ MVI issue - ignored

That's why we're stuck in between with MV(Whatever) - it shines like MVI but works like MVVM.

Whenever you're ready, here’s how I can help you:

The exact system I used to build 11 apps, with 800+ screens, for 34M users. 10 years of production decisions and the reasoning behind every line, so your architecture survives any change without refactor, and AI builds on it correctly.

Reply

Avatar

or to participate

Keep Reading