MVP — o que é, o padrão Model-View-Presenter em iOS e Android

Autor: IT Sectr Publicado: 2026-02-16 Tempo de leitura: 10 min

MVP (Model-View-Presenter) — um padrão arquitetural onde o Presenter atua como intermediário entre o Model e a View através da interface ViewContract. Diferente do MVC, onde o Controller gerencia diretamente a View através do UIKit, o Presenter não depende do framework — ele trabalha através de abstração, o que o torna testável sem Android SDK ou UIKit. O MVP é amplamente usado no desenvolvimento Android antes do Jetpack e continua relevante para projetos legados. Mais informações no artigo de Martin Fowler.

Principais pontos

  • MVP — três componentes: Model (dados), View (interface), Presenter (lógica e estado)
  • ViewContract — a interface através da qual o Presenter se comunica com a View, garantindo testabilidade
  • Presenter — contém toda a lógica de negócio, independente das classes de plataforma Android/iOS
  • Passive View — a View é maximamente passiva, apenas exibe dados por comandos do Presenter
  • MVP vs MVC — o Presenter é testado com testes unitários, o Controller no MVC depende de UIKit/Android Framework

O que é MVP: a essência do padrão Model-View-Presenter

MVP (Model-View-Presenter) — um padrão arquitetural proposto por Martin Fowler no início dos anos 2000 como evolução do MVC para melhorar a testabilidade da interface do usuário. O Model gerencia dados e lógica de negócio, a View é responsável pela renderização e processamento de entrada do usuário, o Presenter é o componente central que recebe eventos da View, recupera dados do Model e forma o estado para exibição.

A principal diferença entre MVP e MVC — o Presenter não tem uma referência direta à View. Em vez disso, o Presenter interage com a View através da interface ViewContract. A View implementa esta interface e passa-se ao Presenter. Isso quebra a dependência do UIKit (iOS) ou Android Framework — o Presenter pode ser testado isoladamente com uma implementação mock do ViewContract. No MVC o controlador UIViewController atualiza diretamente o UILabel, no MVP o Presenter chama view.showName(name), e a View decide como exibir.

ComponenteResponsabilidadeTestabilidade
ModelDados, lógica de negócio, chamadas de redeTestes unitários (independente de UI)
ViewRenderização de UI, passagem de eventos ao PresenterImplementação mock via interface
PresenterLógica de negócio, gerenciamento de estado, navegaçãoTestes unitários (via mock do ViewContract)

Princípio da Responsabilidade Única no MVP é seguido mais estritamente que no MVC: a View é responsável apenas pela renderização, o Model pelos dados, o Presenter pela lógica e coordenação. Em projetos reais o Presenter ocupa 40–60% do código da tela, a View — 20–30%, o Model — 20–30%. Esta distribuição permite testar a lógica de negócio principal sem iniciar um emulador Android ou simulador iOS.

MVP no Android: Presenter, ViewContract e Activity

MVP no Android usa Activity ou Fragment como View, que implementa ViewContract — uma interface com métodos de exibição de dados. O Presenter é criado na Activity, anexa a View a si mesmo e gerencia o carregamento de dados. Quando a tela gira, a Activity é recriada — o Presenter pode ser preservado através de um retain fragment ou armazenamento externo, resolvendo o problema de perda de estado característico do MVC puro.

kotlin
// ViewContract — interface para o Presenter se comunicar com a View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — camada de lógica testável
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 a interface
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) { /* atualizar UI */ }
    override fun showLoading() { /* mostrar ProgressBar */ }
    override fun hideLoading() { /* ocultar ProgressBar */ }
    override fun showError(message: String) { /* mostrar Snackbar */ }
}

Gerenciamento de ciclo de vida — um problema chave do MVP no Android. A Activity é destruída ao girar a tela, e presenter.attachView() é chamado novamente em onCreate(). Se o carregamento de dados for assíncrono (RxJava, corrotinas), no momento da conclusão a View pode estar detached. A solução — cancelar as assinaturas em detachView() ou usar o Loader da Support Library (para projetos sem Jetpack). Na IT Sectr usamos a combinação MVP + RxJava por anos em projetos comerciais — o padrão é estável mas requer disciplina no gerenciamento de assinaturas.

Retain fragments — um mecanismo para preservar o Presenter ao girar a tela. Um fragment sem UI (setRetainInstance(true)) sobrevive à Activity e mantém uma referência ao Presenter. Quando a Activity é recriada, o fragment passa o mesmo Presenter para a nova Activity. Retain fragments estão obsoletos desde AndroidX, mas seu análogo pré-Jetpack (Fragment.setRetainInstance) ainda funciona em projetos legados. No desenvolvimento moderno o Google recomenda ViewModel em vez de retain fragments.

MVP no iOS: Presenter e View Protocol

MVP no iOS é construído através de um protocolo View. UIViewController implementa o protocolo, o Presenter não importa UIKit e é puramente testável. Diferente do Apple MVC, onde UIViewController contém lógica e conexões IBOutlet diretas, o Presenter gerencia o estado e comanda a View através de métodos do protocolo. A View não toma decisões — ela executa comandos do Presenter: showUser, showLoading, navigateToProfile.

swift
import Foundation

// View Protocol — abstração para o Presenter
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — lógica pura, sem 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 o 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
    }
    // ... restante dos métodos do protocolo
}

Weak reference à View — obrigatória no iOS MVP. Um UIViewController pode ser destruído (pop da navigation stack), e seu closure no Presente criaria um retain cycle. Uma referência fraca (weak var) garante que a View seja liberada quando sai da tela, independentemente de operações assíncronas no Presenter. No Android um problema similar é resolvido através de detachView() — chamá-lo em onDestroy() anula a referência à View.

Passive View vs Supervising Controller — duas variantes do MVP por Martin Fowler. Passive View: a View não contém lógica, o Presenter gerencia completamente o estado. Supervising Controller: a View mesma faz binding simples de dados (por exemplo, através de data binding), o Presenter só intervém em cenários complexos. No desenvolvimento mobile o Passive View é usado com mais frequência — ele proporciona máxima testabilidade e previsibilidade do estado da tela.

Diferenças entre MVP e MVC e vantagens de teste

A principal diferença entre MVP e MVC é a forma de comunicação com a View. No MVC o Controller tem uma referência direta à View (UIViewController.IBOutlets, Activity.findViewById). No MVP o Presenter interage com a View através da interface ViewContract. Esta diferença muda fundamentalmente a testabilidade: um objeto mock implementando ViewContract permite testar a lógica do Presenter sem iniciar o aplicativo, emulador ou framework de UI.

CritérioMVCMVP
Conexão com ViewDireta (Controller → View)Através de interface (Presenter → ViewContract)
Teste de lógicaRequer UIKit/Android FrameworkTestes unitários sem dependências de plataforma
Ciclo de vidaController vive com a telaPresenter pode sobreviver (retain)
ComplexidadeMínima+1 interface por tela
Massive ControllerProblema típicoLógica no Presenter, View fina

Exemplo de teste unitário do Presenter em Kotlin: um mock UserView é criado, passado ao Presenter, loadUser é chamado, verifica-se que showUser foi chamado com dados corretos. O teste executa em milissegundos, não requer emulador. No iOS similarmente — OCMock ou um stub de protocolo verifica chamadas de métodos UserViewProtocol. Em projetos da IT Sectr com MVP, a cobertura de testes unitários da lógica de negócio alcançava 85–90%, 2–3 vezes maior que em projetos MVC similares.

Quando MVP é preferível ao MVC — em projetos com requisitos rigorosos de estabilidade: aplicativos bancários, sistemas médicos, terminais de pagamento. Nestes domínios o custo de um erro é alto, e os testes unitários são críticos. Em projetos pós-MVP (quando o produto já está no mercado mas a base de código é legada), o MVP permite extrair gradualmente a lógica do Massive View Controller para uma camada testável sem uma reescrita arquitetônica completa.

Limitações do MVP e transição para MVVM

As principais desvantagens do MVP — o crescimento do número de interfaces e o gerenciamento manual de assinaturas. Cada tela requer pelo menos um ViewContract + Presenter, para 50 telas — 50 interfaces e 50 classes Presenter. No MVVM, o ViewModel substitui o Presenter e usa mecanismos reativos (LiveData, StateFlow, ObservableObject), eliminando a necessidade de attach/detach manual e interfaces ViewContract.

RxJava e MVP — uma combinação popular no Android 2015–2019. O Presenter assina um Observable do Repository, exibe o resultado através do ViewContract. O problema: disposable deve ser cancelado explicitamente em detachView(), caso contrário um vazamento de assinatura causará uma falha ao atualizar uma View detached. As bibliotecas RxLifecycle e AutoDispose automatizaram parcialmente o cancelamento mas adicionaram dependências. Na IT Sectr migramos de MVP+RxJava para MVVM+Flow em 2020 — o código ficou 25–30% mais curto devido à eliminação do ViewContract.

Migração de MVP para MVVM — um processo gradual. 1) Substituir ViewContract por LiveData/StateFlow no Presenter. 2) Remover métodos attach/detach — a assinatura vai através de observe(). 3) Renomear Presenter para ViewModel. 4) Integrar DI (Hilt/Koin) para ViewModelFactory. Migrar uma tela leva 2–4 horas, toda a base de código — 2–4 semanas para um projeto de 50–100 telas. Após a migração, as interfaces ViewContract são removidas, o código diminui, os testes permanecem.

MVP no desenvolvimento moderno — o padrão está vivo mas cede para MVVM e MVI. O Google recomenda oficialmente MVVM com Jetpack para novos projetos. A Apple — MVVM com SwiftUI. No entanto, o conhecimento de MVP é obrigatório para trabalhar com código legado: centenas de aplicativos Android no Google Play ainda funcionam com MVP, incluindo aplicativos de grandes bancos, varejistas e empresas de transporte. Entender MVP é a base para dominar MVI e Clean Architecture, já que o Presenter é o predecessor direto de Use Case nos termos de Robert Martin.

Perguntas Frequentes

Como o MVP difere do MVC?

No MVP, o Presenter interage com a View através da interface ViewContract, não diretamente. No MVC, o Controller tem uma referência direta à View através de IBOutlet/findViewById. O MVP permite testar o Presenter com testes unitários sem iOS Simulator ou Android Emulator, já que o Presenter não depende de UIKit ou Android Framework. O MVC requer iniciar o aplicativo para testar o controlador.

Quando devo usar MVP em vez de MVVM?

O MVP é justificado em projetos legados já construídos sobre este padrão, e em aplicativos sem suporte a mecanismos reativos (LiveData, StateFlow, Combine). Para novos projetos o Google recomenda MVVM com Jetpack (Android) e a Apple recomenda MVVM com SwiftUI (iOS). O MVP continua sendo a melhor escolha para projetos em UIKit puro sem Combine quando testes unitários da lógica de negócio são necessários.

Como resolver o problema de perda do Presenter ao girar a tela?

No Android — usar um retain fragment (setRetainInstance(true)) ou ViewModel do Jetpack. O retain fragment armazena o Presenter ao girar e o passa para a nova Activity. O ViewModel do Google é uma alternativa moderna que preserva o estado automaticamente ao girar sem retain fragments. No iOS — o Presenter é recriado em cada viewDidLoad mas é armazenado em cache em um serviço coordenador separado.

Quantas classes são necessárias para uma tela no MVP?

Pelo menos 4: interface ViewContract, implementação do ViewContract (Activity/Fragment), Presenter, Model (Repository). Se Dagger/Hilt for usado, um módulo DI é adicionado. Para 50 telas são 200+ classes. O MVVM reduz a quantidade em 1 arquivo por tela (ViewContract não é necessário), o MVI adiciona classes State e Intent. O número de classes é o principal argumento contra o MVP em projetos grandes.

Qual a diferença entre Passive View e Supervising Controller no MVP?

Passive View — a View não contém lógica, o Presenter gerencia completamente o estado e os dados. Supervising Controller — a View mesma faz binding simples (data binding), o Presenter intervém em cenários complexos. No desenvolvimento mobile o Passive View domina — proporciona máxima testabilidade e previsibilidade. O Supervising Controller é usado em frameworks web (ASP.NET Web Forms, GWT).

Resumo

  • MVP (Model-View-Presenter) — uma evolução do MVC com uma camada Presenter testável através da interface ViewContract
  • ViewContract — uma interface que abstrai a View do Presenter, permitindo teste com mock
  • Passive View — a variante dominante do MVP no desenvolvimento mobile com uma View passiva
  • Presenter — contém a lógica de negócio, independente de UIKit ou Android Framework
  • MVP vs MVC — o MVP resolve o problema de teste mas adiciona 1 interface por tela
  • Retain fragments do Android — preservação do Presenter ao girar a tela antes do Jetpack ViewModel
  • Migração para MVVM — substituir ViewContract por LiveData/StateFlow reduz o código em 25–30%

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também