MVP — ce este, modelul Model-View-Presenter în iOS și Android

Autor: IT Sectr Publicat: 2026-02-16 Timp de citire: 10 min

MVP (Model-View-Presenter) — model arhitectural în care Presenter acționează ca mediator între Model și View prin intermediul interfeței ViewContract. Spre deosebire de MVC, unde Controller gestionează direct View prin UIKit, Presenter nu depinde de framework — lucrează prin abstractizare, ceea ce îl face testabil fără Android SDK sau UIKit. MVP este utilizat pe scară largă în dezvoltarea Android înainte de apariția Jetpack și rămâne relevant pentru proiectele legacy. Mai multe detalii în articolul lui Martin Fowler.

Puncte principale

  • MVP — trei componente: Model (date), View (interfață), Presenter (logică și stare)
  • ViewContract — interfața prin care Presenter comunică cu View, asigurând testabilitatea
  • Presenter — conține toată logica de business, nu depinde de clasele platformei Android/iOS
  • Passive View — View este maxim pasivă, doar afișează datele la comenzile Presenter
  • MVP vs MVC — Presenter este testat prin teste unitare, Controller în MVC depinde de UIKit/Android Framework

Ce este MVP: esența modelului Model-View-Presenter

MVP (Model-View-Presenter) — model arhitectural propus de Martin Fowler la începutul anilor 2000 ca evoluție a MVC pentru îmbunătățirea testabilității interfeței de utilizator. Model gestionează datele și logica de business, View se ocupă de afișare și procesarea intrărilor utilizatorului, Presenter — componenta centrală care primește evenimente de la View, extrage date din Model și formează starea pentru afișare.

Diferența principală dintre MVP și MVC — Presenter nu are o referință directă la View. În schimb, Presenter interacționează cu View prin interfața ViewContract. View implementează această interfață și se transmite pe sine Presenter. Aceasta rupe dependența de UIKit (iOS) sau Android Framework — Presenter este testat izolat cu o implementare mock a ViewContract. În MVC controlerul UIViewController actualizează direct UILabel, în MVP Presenter apelează metoda view.showName(name), iar View decide cum să afișeze.

ComponentăResponsabilitateTestabilitate
ModelDate, logică de business, apeluri de rețeaTeste unitare (nu depinde de UI)
ViewAfișarea UI, transmiterea evenimentelor la PresenterImplementare mock prin interfață
PresenterLogică de business, gestionarea stării, navigareTeste unitare (prin mock ViewContract)

Principiul responsabilității unice în MVP este respectat mai strict decât în MVC: View răspunde doar de randare, Model — de date, Presenter — de logică și coordonare. În proiectele reale Presenter ocupă 40–60% din codul ecranului, View — 20–30%, Model — 20–30%. Această distribuție permite testarea logicii de business cheie fără a porni emulatorul Android sau simulatorul iOS.

MVP în Android: Presenter, ViewContract și Activity

MVP în Android utilizează Activity sau Fragment ca View care implementează ViewContract — o interfață cu metode de afișare a datelor. Presenter este creat în Activity, abonează View la sine și gestionează încărcarea datelor. La rotirea ecranului Activity este recreat — Presenter poate fi păstrat printr-un retain-fragment sau stocare externă, ceea ce rezolvă problema pierderii stării caracteristică MVC-ului pur.

kotlin
// ViewContract — interfață pentru comunicarea Presenter cu View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — strat de logică testabil
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 ?: "Eroare necunoscută")
            }
        }
    }
}

// View (Activity) implementează interfața
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) { /* actualizează UI */ }
    override fun showLoading() { /* afișează ProgressBar */ }
    override fun hideLoading() { /* ascunde ProgressBar */ }
    override fun showError(message: String) { /* afișează Snackbar */ }
}

Gestionarea ciclului de viață — problema cheie a MVP pe Android. Activity este distrus la rotirea ecranului, iar presenter.attachView() este apelat din nou în onCreate(). Dacă încărcarea datelor este asincronă (RxJava, corutine), la momentul finalizării View poate fi detașată. Soluție — anularea abonamentelor în detachView() sau utilizarea Loader din Support Library (pentru proiecte fără Jetpack). La IT Sectr am folosit ani de zile combinația MVP + RxJava în proiecte comerciale — modelul este stabil, dar necesită disciplină în gestionarea abonamentelor.

Retain-fragment-ele — mecanism de păstrare a Presenter la rotirea ecranului. Fragmentul fără UI (setRetainInstance(true)) trăiește mai mult decât Activity și păstrează referința la Presenter. La recrearea Activity, fragmentul transmite același Presenter noii Activity. Retain-fragment-ele sunt depreciate din AndroidX, dar echivalentul lor pre-Jetpack (Fragment.setRetainInstance) încă funcționează în proiectele legacy. În dezvoltarea modernă Google recomandă ViewModel în locul retain-fragment-elor.

MVP în iOS: Presenter și View Protocol

MVP în iOS este construit printr-un protocol View. UIViewController implementează protocolul, Presenter nu importă UIKit și este testat pur. Spre deosebire de Apple MVC, unde UIViewController conține el însuși logica și conexiunile directe IBOutlet, Presenter gestionează starea și comandă View prin metodele protocolului. View nu ia decizii — execută comenzile Presenter: showUser, showLoading, navigateToProfile.

swift
import Foundation

// View Protocol — abstractizare pentru Presenter
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — logică pură, fără 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) implementează protocolul
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
    }
    // ... restul metodelor protocolului
}

Weak reference la View — o condiție obligatorie în iOS MVP. UIViewController poate fi distrus (pop din stiva de navigare), iar închiderea sa în Presenter va crea un retain cycle. Referința slabă (weak var) garantează că View este eliberată când părăsește ecranul, indiferent de prezența operațiunilor asincrone în Presenter. În Android o problemă similară se rezolvă prin detachView() — apelul în onDestroy() anulează referința la View.

Passive View vs Supervising Controller — două variante MVP de la Martin Fowler. Passive View: View nu conține logică, Presenter gestionează complet starea. Supervising Controller: View face singură legarea simplă a datelor (de exemplu, prin data binding), Presenter intervine doar în scenarii complexe. În dezvoltarea mobilă Passive View este folosit mai des — oferă testabilitate maximă și predictibilitate a stării ecranului.

Diferențele dintre MVP și MVC și avantajele testării

Diferența principală dintre MVP și MVC — modul de comunicare cu View. În MVC Controller are o referință directă la View (UIViewController.IBOutlets, Activity.findViewById). În MVP Presenter interacționează cu View prin interfața ViewContract. Această diferență schimbă radical testabilitatea: un obiect Mock care implementează ViewContract permite verificarea logicii Presenter fără a porni aplicația, emulatorul și framework-ul UI.

CriteriuMVCMVP
Legătura cu ViewDirectă (Controller → View)Prin interfață (Presenter → ViewContract)
Testarea logiciiNecesită UIKit/Android FrameworkTeste unitare fără dependențe de platformă
Ciclul de viațăController trăiește cu ecranulPresenter poate trăi mai mult (retain)
ComplexitateMinimă+1 interfață per ecran
Massive ControllerProblemă tipicăLogica în Presenter, View subțire

Exemplu de test unitar Presenter în Kotlin: se creează un mock UserView, se transmite Presenter, se apelează loadUser, se verifică că showUser a fost apelat cu date corecte. Testul se execută în milisecunde, nu necesită emulator. Pe iOS similar — OCMock sau un stub de protocol verifică apelurile metodelor UserViewProtocol. În proiectele IT Sectr cu MVP, acoperirea logicii de business cu teste unitare ajungea la 85–90%, de 2–3 ori mai mult decât în proiecte MVC similare.

Când MVP este preferabil MVC — în proiecte cu cerințe stricte de stabilitate: aplicații bancare, sisteme medicale, terminale de plată. În aceste domenii costul erorii este ridicat, iar testele unitare sunt critice. În proiectele Post-MVP (când produsul este deja pe piață, dar baza de cod este legacy) MVP permite mutarea treptată a logicii din Massive View Controller într-un strat testabil fără rescrierea completă a arhitecturii.

Limitările MVP și tranziția la MVVM

Principalele dezavantaje ale MVP — creșterea numărului de interfețe și gestionarea manuală a abonamentelor. Fiecare ecran necesită cel puțin un ViewContract + Presenter, pentru 50 de ecrane — 50 de interfețe și 50 de clase Presenter. În MVVM ViewModel înlocuiește Presenter și utilizează mecanisme reactive (LiveData, StateFlow, ObservableObject), ceea ce elimină necesitatea attach/detach manual și a interfețelor ViewContract.

RxJava și MVP — combinație populară în Android 2015–2019. Presenter se abonează la Observable din Repository, afișează rezultatul prin ViewContract. Problemă: disposable trebuie anulat explicit în detachView(), altfel scurgerea abonamentului va provoca un crash la actualizarea View detașate. Bibliotecile RxLifecycle și AutoDispose au automatizat parțial dezabonarea, dar au adăugat dependență. La IT Sectr am trecut de la MVP+RxJava la MVVM+Flow în 2020 — codul s-a scurtat cu 25–30% datorită eliminării ViewContract.

Migrarea de la MVP la MVVM — proces în etape. 1) Înlocuirea ViewContract cu LiveData/StateFlow în Presenter. 2) Eliminarea metodelor attach/detach — abonarea se face prin observe(). 3) Redenumirea Presenter în ViewModel. 4) Integrarea DI (Hilt/Koin) pentru ViewModelFactory. Migrarea unui ecran durează 2–4 ore, a întregii baze de cod — 2–4 săptămâni pentru un proiect de 50–100 de ecrane. După migrare interfețele ViewContract sunt șterse, codul se scurtează, testele rămân.

MVP în dezvoltarea modernă — modelul trăiește, dar cedează în fața MVVM și MVI. Google recomandă oficial MVVM cu Jetpack pentru proiecte noi. Apple — MVVM cu SwiftUI. Cu toate acestea, cunoașterea MVP este obligatorie pentru lucrul cu cod legacy: sute de aplicații Android în Google Play încă funcționează pe MVP, inclusiv aplicații ale marilor bănci, retaileri și companii de transport. Înțelegerea MVP este fundamentul pentru stăpânirea MVI și Clean Architecture, deoarece Presenter este predecesorul direct al Use Case în terminologia lui Robert Martin.

Întrebări frecvente

Cu ce se deosebește MVP de MVC?

În MVP Presenter interacționează cu View prin interfața ViewContract, nu direct. În MVC Controller are o referință directă la View prin IBOutlet/findViewById. MVP permite testarea Presenter cu teste unitare fără iOS Simulator sau Android Emulator, deoarece Presenter nu depinde de UIKit sau Android Framework. MVC necesită pornirea aplicației pentru testarea controlerului.

Când ar trebui să folosesc MVP în loc de MVVM?

MVP este justificat în proiectele legacy deja construite pe acest model și în aplicațiile fără suport pentru mecanisme reactive (LiveData, StateFlow, Combine). Pentru proiecte noi Google recomandă MVVM cu Jetpack (Android), iar Apple recomandă MVVM cu SwiftUI (iOS). MVP rămâne cea mai bună alegere pentru proiecte pe UIKit pur fără Combine când se cere testarea unitară a logicii de business.

Cum se rezolvă problema pierderii Presenter la rotirea ecranului?

Pe Android — folosind retain-fragment (setRetainInstance(true)) sau ViewModel din Jetpack. Retain-fragment păstrează Presenter la rotire și îl transmite noii Activity. ViewModel de la Google — o alternativă modernă care păstrează automat starea la rotire fără retain-fragment-e. Pe iOS — Presenter este creat din nou la fiecare viewDidLoad, dar este cache-uit într-un serviciu coordinator separat.

Câte clase sunt necesare pentru un ecran în MVP?

Minimum 4: interfața ViewContract, implementarea ViewContract (Activity/Fragment), Presenter, Model (Repository). Dacă se folosesc Dagger/Hilt, se adaugă un modul DI. Pentru 50 de ecrane sunt 200+ clase. MVVM reduce numărul cu 1 fișier per ecran (ViewContract nu e necesar), MVI adaugă clasele State și Intent. Numărul de clase — principalul argument împotriva MVP în proiecte mari.

Care este diferența dintre Passive View și Supervising Controller în MVP?

Passive View — View nu conține logică, Presenter gestionează complet starea și datele. Supervising Controller — View face singură legarea simplă (data binding), Presenter intervine în scenarii complexe. În dezvoltarea mobilă domină Passive View — oferă testabilitate maximă și predictibilitate. Supervising Controller este folosit în framework-uri web (ASP.NET Web Forms, GWT).

Concluzii

  • MVP (Model-View-Presenter) — evoluția MVC cu un strat Presenter testabil prin interfața ViewContract
  • ViewContract — interfață care abstractizează View de Presenter, permițând testarea mock
  • Passive View — varianta dominantă MVP în dezvoltarea mobilă cu View pasiv
  • Presenter — conține logica de business, nu depinde de UIKit sau Android Framework
  • MVP vs MVC — MVP rezolvă problema testării, dar adaugă 1 interfață per ecran
  • Retain-fragment-ele Android — păstrarea Presenter la rotirea ecranului înainte de apariția Jetpack ViewModel
  • Migrarea la MVVM — înlocuirea ViewContract cu LiveData/StateFlow scurtează codul cu 25–30%

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și