Sitemap

MVC → MVP → MVVM → MVI: The Evolution of Android Architecture Patterns

4 min readJun 28, 2026

--

Four patterns that everyone treats as separate choices are really one idea that kept evolving — each fixing the pain of the one before it.

Press enter or click to view image in full size

There are four architecture patterns widely used in Android development: MVC, MVP, MVVM, and MVI. It’s tempting to think of them as four competing options, but they’re not four random ideas. They’re a single idea that kept evolving, with each step solving a problem the previous one created.

Every one of them is really answering the same two questions:

  1. Who holds the data?
  2. How does the UI talk to the logic?

Let’s walk the timeline.

MVC — Model, View, Controller

MVC was the go-to in the early days of Android.

  • Model is your data.
  • View is the screen.
  • Controller is the middleman that handles input.

In practice on Android, your Activity ends up doing almost everything — it calls the API, processes the data, and updates the UI.

class UserActivity : AppCompatActivity() {
fun loadUser() {
api.getUser { user -> // calls the API
val name = user.name.uppercase() // processes data
nameTextView.text = name // updates the UI
}
}
}

It’s simple to write — but it gets messy fast. The logic and the UI updates both live inside the Activity, tangled together and almost impossible to test.

Press enter or click to view image in full size

So people asked: how do we pull the logic out of the view?

MVP — Model, View, Presenter

That question led to MVP, where all the logic moves into a separate class: the Presenter.

The View and Presenter talk through an interface, which means you can test the Presenter with no real screen at all.

interface UserView {
fun showName(name: String)
}

class UserPresenter(private val view: UserView) {
fun loadUser() {
api.getUser { user ->
view.showName(user.name.uppercase()) // commands the view
}
}
}

Notice the connection: the Presenter holds the View and commands it. That two-way link is the new pain. It means a lot of boilerplate, and a memory leak if you forget to clean up the reference when the screen is destroyed.

Press enter or click to view image in full size

So the next question became: how can we connect them without holding onto each other?

MVVM — Model, View, ViewModel

That brings us to MVVM, what most Android apps use today.

The big upgrade: the View observes the ViewModel, but the ViewModel never knows the View exists.

class UserViewModel : ViewModel() {
private val _name = MutableStateFlow("")
val name: StateFlow<String> = _name // no View reference anywhere

fun loadUser() {
api.getUser { user ->
_name.value = user.name.uppercase() // just changes data
}
}
}

There’s no View anywhere inside the ViewModel. It just changes data, and the UI reacts on its own to the StateFlow. No two-way references, no manual updates — and it even survives screen rotation.

Press enter or click to view image in full size

But on big, complex screens, one problem shows up: state gets scattered across many separate variables, and it’s easy for the UI to fall out of sync.

Press enter or click to view image in full size

So the final question: what if the whole screen had just one state?

MVI — Model, View, Intent

That’s why MVI came into the picture. In MVI, everything revolves around a single state.

  • User actions create intents.
  • Intents update the state.
  • The UI reacts to that state.
Press enter or click to view image in full size

The entire screen is one single state object — one source of truth — and data flows in one direction.

data class UserState(
val isLoading: Boolean = false,
val name: String = "",
val error: String? = null
)

class UserViewModel : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state
fun onIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUser -> {
_state.value = _state.value.copy(isLoading = true)
api.getUser { user ->
_state.value = UserState(name = user.name.uppercase())
}
}
}
}
}

One state object describes the whole screen, so the UI can never drift out of sync — it always renders exactly what the state says.

The whole evolution in one breath

  • MVC tangled everything together.
  • MVP pulled the logic out for testing, but tied the View and Presenter too tightly.
  • MVVM cut that knot with observable data — the safe default today.
  • MVI went further, with one single state for complex, reactive screens.

Which one should you use?

For most apps, start with MVVM. It’s the modern default, it survives configuration changes, and it keeps your ViewModel free of any View references.

Reach for MVI when a screen gets complex enough that scattered state becomes a real source of bugs — that single source of truth pays for its extra structure.

If this helped, follow for more Android tips — and watch the full walkthrough on my YouTube channel.

--

--

KotlinGeek
KotlinGeek

Written by KotlinGeek

Breaking down Android and Kotlin concepts into simple, practical posts. Here to make the hard stuff click.