← artículos
AndroidArquitecturaKotlinJetpack Compose

Patrones de diseño en Android: MVC, MVP, MVVM y MVI. Diferencias y cuándo usarlos

6 de julio de 2026
Patrones de diseño en Android: MVC, MVP, MVVM y MVI. Diferencias y cuándo usarlos

En Android, elegir una arquitectura no consiste en seguir una moda. El patrón que adoptes condiciona cómo organizas la lógica de presentación, cómo gestionas el estado de la pantalla y cuánto costará mantener la aplicación cuando crezca.

MVC, MVP, MVVM y MVI comparten un objetivo: evitar que Activity, Fragment o un composable concentren toda la responsabilidad de una pantalla. Sin embargo, cada patrón distribuye el trabajo de forma diferente y encaja mejor en ciertos escenarios.

El problema: pantallas con demasiadas responsabilidades

Una pantalla Android suele recibir eventos del usuario, pedir datos a un repositorio, transformar esos datos, mostrar estados de carga y gestionar errores. Cuando todo ocurre en una Activity o Fragment, el código se vuelve difícil de probar y modificar.

Una arquitectura saludable busca separar al menos estas piezas:

  • Vista: muestra información y recoge interacciones.
  • Lógica de presentación: decide qué debe mostrar la vista.
  • Datos o dominio: obtiene, transforma y persiste información.
  • Estado: representa la situación actual de la pantalla.

MVC: el enfoque clásico

MVC significa Model-View-Controller. Divide la aplicación en tres partes:

  • Model: datos y reglas de negocio.
  • View: interfaz que el usuario ve.
  • Controller: recibe acciones de la vista y coordina cambios en el modelo.

En Android tradicional, una Activity o Fragment suele terminar haciendo de controlador. El problema es que también contiene referencias a vistas, lógica de navegación, llamadas a datos y manejo del ciclo de vida. Esto puede producir clases muy grandes.

class ProfileActivity : AppCompatActivity() {

    private val repository = UserRepository()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            val user = repository.getUser()
            findViewById<TextView>(R.id.nameText).text = user.name
        }
    }
}

Este ejemplo funciona, pero la pantalla depende directamente del repositorio y actualiza la interfaz por sí misma.

Cuándo usar MVC

MVC puede funcionar bien en:

  • Aplicaciones pequeñas.
  • Prototipos rápidos.
  • Pantallas con lógica mínima.
  • Proyectos antiguos donde cambiar toda la arquitectura no es viable.

Su principal riesgo aparece cuando el controlador crece demasiado. En Android, esto suele derivar en las conocidas Massive Activities o Massive Fragments.

MVP: una vista más pasiva

MVP significa Model-View-Presenter. La diferencia clave respecto a MVC es que el Presenter contiene la lógica de presentación y se comunica con una interfaz de vista.

La vista expone métodos como showLoading() o showUser(). El presenter decide cuándo invocarlos.

interface ProfileView {
    fun showLoading()
    fun showUser(name: String)
    fun showError(message: String)
}

class ProfilePresenter(
    private val view: ProfileView,
    private val repository: UserRepository
) {
    suspend fun loadProfile() {
        view.showLoading()

        runCatching { repository.getUser() }
            .onSuccess { user -> view.showUser(user.name) }
            .onFailure { error -> view.showError(error.message ?: "Error inesperado") }
    }
}

La Activity o el Fragment implementan ProfileView, mientras que el presenter puede probarse sin depender directamente de componentes Android.

Ventajas de MVP

  • Separa mejor la lógica de la interfaz.
  • Facilita pruebas unitarias del presenter.
  • Hace explícita la comunicación entre vista y presentación.

Limitaciones de MVP

El presenter suele mantener una referencia a la vista. Esto obliga a gestionar con cuidado el ciclo de vida para evitar llamadas cuando la pantalla ya no existe. Además, interfaces con muchos estados pueden acabar con decenas de métodos como showEmptyState(), hideLoading() o showRetryButton().

Cuándo usar MVP

MVP sigue siendo útil en proyectos basados en vistas XML, especialmente cuando ya existe una base de código madura con presenters. También puede encajar en equipos que prefieren interfaces explícitas entre la vista y la lógica.

MVVM: estado observable y ViewModel

MVVM significa Model-View-ViewModel. Es uno de los patrones más usados en Android moderno porque encaja de forma natural con ViewModel, StateFlow, LiveData y Jetpack Compose.

La vista observa un estado expuesto por el ViewModel. En lugar de recibir órdenes como showLoading(), la interfaz se renderiza según los datos disponibles.

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

class ProfileViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(ProfileUiState())
    val uiState: StateFlow<ProfileUiState> = _uiState

    fun loadProfile() {
        viewModelScope.launch {
            _uiState.value = ProfileUiState(isLoading = true)

            _uiState.value = runCatching { repository.getUser() }
                .fold(
                    onSuccess = { user -> ProfileUiState(name = user.name) },
                    onFailure = { error ->
                        ProfileUiState(error = error.message ?: "Error inesperado")
                    }
                )
        }
    }
}

En Compose, la interfaz puede observar este estado y dibujarse de forma declarativa.

@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
    val state by viewModel.uiState.collectAsState()

    when {
        state.isLoading -> CircularProgressIndicator()
        state.error != null -> Text(state.error)
        state.name != null -> Text("Hola, ${state.name}")
    }
}

Cuándo usar MVVM

MVVM es una opción sólida para la mayoría de aplicaciones Android actuales, especialmente cuando:

  • Usas Jetpack Compose.
  • Necesitas sobrevivir a cambios de configuración.
  • Trabajas con flujos de datos reactivos.
  • Quieres separar interfaz y lógica sin crear interfaces de vista extensas.
  • La pantalla tiene varios estados de carga, éxito o error.

MVI: estado único y eventos explícitos

MVI significa Model-View-Intent. Su idea central es que la pantalla tiene una única fuente de verdad: un estado completo e inmutable. La vista envía intenciones, el sistema las procesa y genera un nuevo estado.

El flujo suele ser unidireccional:

Usuario → Intent → ViewModel o Reducer → Nuevo estado → Vista

data class LoginUiState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

sealed interface LoginIntent {
    data class EmailChanged(val value: String) : LoginIntent
    data class PasswordChanged(val value: String) : LoginIntent
    data object Submit : LoginIntent
}

Cada acción produce una transición de estado predecible. Esto facilita entender qué ha ocurrido en una pantalla compleja y reduce inconsistencias entre elementos visuales.

Cuándo usar MVI

MVI destaca cuando la interfaz tiene muchas interacciones o estados combinados:

  • Formularios complejos.
  • Flujos de pago o registro.
  • Búsquedas con filtros.
  • Pantallas de comercio electrónico.
  • Interfaces con Compose y renderizado declarativo.
  • Casos donde necesitas trazabilidad clara de eventos.

Su coste es una mayor cantidad de código: intents, reducers, efectos y estados. Para una pantalla simple, puede ser más estructura de la necesaria.

Comparativa rápida

Patrón Comunicación principal Punto fuerte Riesgo habitual
MVC Vista y controlador Simplicidad inicial Actividades o fragments demasiado grandes
MVP Vista mediante interfaz y presenter Testabilidad explícita Muchas llamadas y métodos de vista
MVVM Estado observable desde ViewModel Equilibrio entre simplicidad y escalabilidad Lógica dispersa si el estado no está bien modelado
MVI Eventos y estado único unidireccional Predictibilidad en pantallas complejas Exceso de abstracción en casos pequeños

Qué patrón elegir

Para una aplicación Android nueva, MVVM suele ser el punto de partida más equilibrado. Funciona especialmente bien con componentes Jetpack y con Compose, permite estados observables y mantiene las pantallas más limpias.

Elige MVI cuando necesites un flujo de estado muy controlado, múltiples interacciones o una interfaz donde cada transición deba ser fácil de rastrear.

Mantén MVP cuando trabajes en una aplicación basada en XML con una arquitectura ya consolidada, o cuando tu equipo valore una separación muy explícita entre vista y lógica.

Usa MVC solo para casos pequeños o código legado sencillo. No es incorrecto, pero escala peor cuando la aplicación empieza a acumular reglas y estados.

La mejor arquitectura es la que permite que el equipo entienda una pantalla rápidamente, escriba pruebas con confianza y haga cambios sin romper comportamientos aparentemente ajenos.

Mario Ríos Holgado

escrito por

Mario Ríos Holgado