MVP — qué es, el patrón Model-View-Presenter en iOS y Android

Autor: IT Sectr Publicado: 2026-02-16 Tiempo de lectura: 10 min

MVP (Model-View-Presenter) es un patrón arquitectónico donde el Presenter actúa como intermediario entre el Model y la View a través de la interfaz ViewContract. A diferencia de MVC, donde el Controller gestiona directamente la View a través de UIKit, el Presenter no depende del framework — trabaja mediante abstracción, lo que lo hace testeable sin Android SDK ni UIKit. MVP se usa ampliamente en desarrollo Android antes de Jetpack y sigue siendo relevante para proyectos legacy. Más información en el artículo de Martin Fowler.

Puntos clave

  • MVP — tres componentes: Model (datos), View (interfaz), Presenter (lógica y estado)
  • ViewContract — la interfaz a través de la cual el Presenter se comunica con la View, garantizando la testeabilidad
  • Presenter — contiene toda la lógica de negocio, independiente de las clases de plataforma Android/iOS
  • Passive View — la View es máximamente pasiva, solo muestra datos por comandos del Presenter
  • MVP vs MVC — el Presenter se prueba con tests unitarios, el Controller en MVC depende de UIKit/Android Framework

Qué es MVP: la esencia del patrón Model-View-Presenter

MVP (Model-View-Presenter) es un patrón arquitectónico propuesto por Martin Fowler a principios de los 2000 como evolución de MVC para mejorar la testeabilidad de la interfaz de usuario. Model gestiona los datos y la lógica de negocio, View se encarga del renderizado y procesamiento de entrada del usuario, Presenter es el componente central que recibe eventos de View, obtiene datos de Model y forma el estado para la visualización.

La principal diferencia entre MVP y MVC — el Presenter no tiene una referencia directa a la View. En su lugar, el Presenter interactúa con la View a través de la interfaz ViewContract. La View implementa esta interfaz y se pasa al Presenter. Esto rompe la dependencia de UIKit (iOS) o Android Framework — el Presenter se puede probar de forma aislada con una implementación mock de ViewContract. En MVC el controlador UIViewController actualiza directamente UILabel, en MVP el Presenter llama a view.showName(name), y la View decide cómo mostrarlo.

ComponenteResponsabilidadTesteabilidad
ModelDatos, lógica de negocio, llamadas de redTests unitarios (independiente de UI)
ViewRenderizado de UI, paso de eventos al PresenterImplementación mock mediante interfaz
PresenterLógica de negocio, gestión de estado, navegaciónTests unitarios (mediante mock de ViewContract)

Principio de Responsabilidad Única en MVP se sigue más estrictamente que en MVC: View solo es responsable del renderizado, Model de los datos, Presenter de la lógica y coordinación. En proyectos reales el Presenter ocupa 40–60% del código de la pantalla, View — 20–30%, Model — 20–30%. Esta distribución permite probar la lógica de negocio clave sin lanzar un emulador de Android o simulador de iOS.

MVP en Android: Presenter, ViewContract y Activity

MVP en Android usa Activity o Fragment como View, que implementa ViewContract — una interfaz con métodos de visualización de datos. El Presenter se crea en la Activity, adjunta la View a sí mismo y gestiona la carga de datos. Cuando la pantalla rota, la Activity se recrea — el Presenter se puede conservar mediante un retain fragment o almacenamiento externo, resolviendo el problema de pérdida de estado característico de MVC puro.

kotlin
// ViewContract — interfaz para que el Presenter se comunique con la View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — capa de lógica testeable
class UserPresenter(
    private val repository: UserRepository
) {
    private var view: UserView? = null

    fun attachView(view: UserView) {
        this.view = view
    }

    fun detachView() {
        view = null
    }

    fun loadUser(userId: Int) {
        view?.showLoading()
        repository.getUser(userId) { result ->
            view?.hideLoading()
            result.onSuccess { user ->
                view?.showUser(user)
            }.onFailure { e ->
                view?.showError(e.message ?: "Unknown error")
            }
        }
    }
}

// View (Activity) implementa la interfaz
class UserActivity : AppCompatActivity(), UserView {
    private val presenter = UserPresenter(UserRepository())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter.attachView(this)
        presenter.loadUser(42)
    }

    override fun onDestroy() {
        presenter.detachView()
        super.onDestroy()
    }

    override fun showUser(user: User) { /* actualizar UI */ }
    override fun showLoading() { /* mostrar ProgressBar */ }
    override fun hideLoading() { /* ocultar ProgressBar */ }
    override fun showError(message: String) { /* mostrar Snackbar */ }
}

Gestión del ciclo de vida es un problema clave de MVP en Android. La Activity se destruye al rotar la pantalla, y presenter.attachView() se llama de nuevo en onCreate(). Si la carga de datos es asíncrona (RxJava, corrutinas), para cuando se completa la View puede estar detached. La solución es cancelar las suscripciones en detachView() o usar el Loader de Support Library (para proyectos sin Jetpack). En IT Sectr usamos la combinación MVP + RxJava durante años en proyectos comerciales — el patrón es estable pero requiere disciplina en la gestión de suscripciones.

Retain fragments — un mecanismo para conservar el Presenter al rotar la pantalla. Un fragment sin UI (setRetainInstance(true)) sobrevive a la Activity y mantiene una referencia al Presenter. Cuando la Activity se recrea, el fragment pasa el mismo Presenter a la nueva Activity. Los retain fragments están obsoletos desde AndroidX, pero su análogo pre-Jetpack (Fragment.setRetainInstance) sigue funcionando en proyectos legacy. En desarrollo moderno Google recomienda ViewModel en lugar de retain fragments.

MVP en iOS: Presenter y View Protocol

MVP en iOS se construye mediante un protocolo View. UIViewController implementa el protocolo, el Presenter no importa UIKit y es puramente testeable. A diferencia de Apple MVC, donde UIViewController contiene lógica y conexiones IBOutlet directas, el Presenter gestiona el estado y comanda la View a través de métodos del protocolo. La View no toma decisiones — ejecuta comandos del Presenter: showUser, showLoading, navigateToProfile.

swift
import Foundation

// View Protocol — abstracción para el Presenter
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — lógica pura, sin UIKit
final class UserPresenter {
    private weak var view: UserViewProtocol?
    private let service: UserServiceProtocol

    init(service: UserServiceProtocol) {
        self.service = service
    }

    func attach(view: UserViewProtocol) {
        self.view = view
    }

    func detach() {
        view = nil
    }

    func loadUser(id: Int) {
        view?.showLoading()
        service.fetchUser(id: id) { [weak self] result in
            guard let self else { return }
            self.view?.hideLoading()
            switch result {
            case .success(let user):
                self.view?.display(user: user)
            case .failure(let error):
                self.view?.displayError(message: error.localizedDescription)
            }
        }
    }
}

// View (UIViewController) implementa el protocolo
final class UserViewController: UIViewController, UserViewProtocol {
    private let presenter = UserPresenter(service: UserService())

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.attach(view: self)
        presenter.loadUser(id: 42)
    }

    func display(user: User) {
        nameLabel.text = user.name
    }
    // ... resto de métodos del protocolo
}

Weak reference a la View es obligatoria en iOS MVP. Un UIViewController puede ser destruido (pop del navigation stack), y su closure en el Presenter crearía un retain cycle. Una referencia débil (weak var) garantiza que la View se libera cuando sale de pantalla, independientemente de operaciones asíncronas en el Presenter. En Android un problema similar se resuelve mediante detachView() — llamarlo en onDestroy() anula la referencia a la View.

Passive View vs Supervising Controller — dos variantes de MVP por Martin Fowler. Passive View: la View no contiene lógica, el Presenter gestiona completamente el estado. Supervising Controller: la View misma hace binding simple de datos (por ejemplo, mediante data binding), el Presenter solo interviene en escenarios complejos. En desarrollo móvil Passive View se usa más a menudo — proporciona máxima testeabilidad y predecibilidad del estado de la pantalla.

Diferencias entre MVP y MVC y ventajas de testing

La principal diferencia entre MVP y MVC es la forma de comunicación con la View. En MVC el Controller tiene una referencia directa a la View (UIViewController.IBOutlets, Activity.findViewById). En MVP el Presenter interactúa con la View a través de la interfaz ViewContract. Esta diferencia cambia fundamentalmente la testeabilidad: un objeto mock que implementa ViewContract permite probar la lógica del Presenter sin lanzar la app, emulador ni framework de UI.

CriterioMVCMVP
Conexión con ViewDirecta (Controller → View)Mediante interfaz (Presenter → ViewContract)
Testing de lógicaRequiere UIKit/Android FrameworkTests unitarios sin dependencias de plataforma
Ciclo de vidaController vive con la pantallaPresenter puede sobrevivir (retain)
ComplejidadMínima+1 interfaz por pantalla
Massive ControllerProblema típicoLógica en Presenter, View delgada

Ejemplo de test unitario del Presenter en Kotlin: se crea un mock UserView, se pasa al Presenter, se llama a loadUser, se verifica que showUser fue llamado con datos correctos. El test se ejecuta en milisegundos, no requiere emulador. En iOS similarmente — OCMock o un stub de protocolo verifica llamadas a métodos de UserViewProtocol. En proyectos de IT Sectr con MVP, la cobertura de tests unitarios de lógica de negocio alcanzaba 85–90%, 2–3 veces más que en proyectos MVC similares.

Cuándo MVP es preferible a MVC — en proyectos con requisitos estrictos de estabilidad: aplicaciones bancarias, sistemas médicos, terminales de pago. En estos ámbitos el coste de un error es alto y los tests unitarios son críticos. En proyectos post-MVP (cuando el producto ya está en el mercado pero la base de código es legacy), MVP permite extraer gradualmente la lógica del Massive View Controller a una capa testeable sin una reescritura arquitectónica completa.

Limitaciones de MVP y transición a MVVM

Las principales desventajas de MVP son el crecimiento del número de interfaces y la gestión manual de suscripciones. Cada pantalla requiere al menos un ViewContract + Presenter, para 50 pantallas — 50 interfaces y 50 clases Presenter. En MVVM, ViewModel reemplaza al Presenter y usa mecanismos reactivos (LiveData, StateFlow, ObservableObject), eliminando la necesidad de attach/detach manual e interfaces ViewContract.

RxJava y MVP — una combinación popular en Android 2015–2019. El Presenter se suscribe a un Observable del Repository, muestra el resultado mediante ViewContract. El problema: disposable debe cancelarse explícitamente en detachView(), de lo contrario una fuga de suscripción causará un crash al actualizar una View detached. Las bibliotecas RxLifecycle y AutoDispose automatizaron parcialmente la cancelación pero añadieron dependencias. En IT Sectr migramos de MVP+RxJava a MVVM+Flow en 2020 — el código se volvió 25–30% más corto gracias a la eliminación de ViewContract.

Migración de MVP a MVVM es un proceso gradual. 1) Reemplazar ViewContract por LiveData/StateFlow en el Presenter. 2) Eliminar métodos attach/detach — la suscripción va mediante observe(). 3) Renombrar Presenter a ViewModel. 4) Integrar DI (Hilt/Koin) para ViewModelFactory. Migrar una pantalla toma 2–4 horas, toda la base de código — 2–4 semanas para un proyecto de 50–100 pantallas. Tras la migración, las interfaces ViewContract se eliminan, el código se reduce, los tests se mantienen.

MVP en el desarrollo moderno — el patrón está vivo pero cede ante MVVM y MVI. Google recomienda oficialmente MVVM con Jetpack para nuevos proyectos. Apple — MVVM con SwiftUI. Sin embargo, el conocimiento de MVP es obligatorio para trabajar con código legacy: cientos de apps Android en Google Play siguen funcionando con MVP, incluyendo apps de grandes bancos, minoristas y empresas de transporte. Entender MVP es la base para dominar MVI y Clean Architecture, ya que el Presenter es el predecesor directo de Use Case en términos de Robert Martin.

Preguntas Frecuentes

¿En qué se diferencia MVP de MVC?

En MVP, el Presenter interactúa con la View a través de la interfaz ViewContract, no directamente. En MVC, el Controller tiene una referencia directa a la View mediante IBOutlet/findViewById. MVP permite probar el Presenter con tests unitarios sin iOS Simulator ni Android Emulator, ya que el Presenter no depende de UIKit ni Android Framework. MVC requiere lanzar la app para probar el controlador.

¿Cuándo debería usar MVP en lugar de MVVM?

MVP está justificado en proyectos legacy ya construidos sobre este patrón, y en apps sin soporte de mecanismos reactivos (LiveData, StateFlow, Combine). Para proyectos nuevos Google recomienda MVVM con Jetpack (Android) y Apple recomienda MVVM con SwiftUI (iOS). MVP sigue siendo la mejor opción para proyectos en UIKit puro sin Combine cuando se requieren tests unitarios de la lógica de negocio.

¿Cómo resolver el problema de pérdida del Presenter al rotar la pantalla?

En Android — usar un retain fragment (setRetainInstance(true)) o ViewModel de Jetpack. El retain fragment almacena el Presenter al rotar y lo pasa a la nueva Activity. ViewModel de Google es una alternativa moderna que conserva el estado automáticamente al rotar sin retain fragments. En iOS — el Presenter se recrea en cada viewDidLoad pero se cachea en un servicio coordinador separado.

¿Cuántas clases se necesitan para una pantalla en MVP?

Al menos 4: interfaz ViewContract, implementación de ViewContract (Activity/Fragment), Presenter, Model (Repository). Si se usa Dagger/Hilt, se añade un módulo DI. Para 50 pantallas son 200+ clases. MVVM reduce la cantidad en 1 archivo por pantalla (ViewContract no es necesario), MVI añade clases State e Intent. El número de clases es el principal argumento contra MVP en proyectos grandes.

¿Cuál es la diferencia entre Passive View y Supervising Controller en MVP?

Passive View — la View no contiene lógica, el Presenter gestiona completamente el estado y los datos. Supervising Controller — la View misma hace binding simple (data binding), el Presenter interviene en escenarios complejos. En desarrollo móvil domina Passive View — proporciona máxima testeabilidad y predecibilidad. Supervising Controller se usa en frameworks web (ASP.NET Web Forms, GWT).

Resumen

  • MVP (Model-View-Presenter) — una evolución de MVC con una capa Presenter testeable mediante la interfaz ViewContract
  • ViewContract — una interfaz que abstrae la View del Presenter, permitiendo testing con mock
  • Passive View — la variante dominante de MVP en desarrollo móvil con una View pasiva
  • Presenter — contiene la lógica de negocio, independiente de UIKit o Android Framework
  • MVP vs MVC — MVP resuelve el problema de testing pero añade 1 interfaz por pantalla
  • Retain fragments de Android — conservación del Presenter al rotar la pantalla antes de Jetpack ViewModel
  • Migración a MVVM — reemplazar ViewContract por LiveData/StateFlow reduce el código 25–30%

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también