MVP — mi ez, a Model-View-Presenter minta iOS-ben és Android-ban

Szerző: IT Sectr Megjelenés: 2026-02-16 Olvasási idő: 10 perc

MVP (Model-View-Presenter) — architekturális minta, amelyben a Presenter közvetítőként működik a Model és a View között a ViewContract interfészen keresztül. Ellentétben az MVC-vel, ahol a Controller közvetlenül irányítja a View-t az UIKit-en keresztül, a Presenter nem függ a keretrendszertől — absztrakción keresztül működik, ami Android SDK vagy UIKit nélkül tesztelhetővé teszi. Az MVP széles körben használták az Android fejlesztésben a Jetpack megjelenése előtt, és továbbra is releváns a legacy projektek számára. Bővebben Martin Fowler cikkében.

Főbb pontok

  • MVP — három komponens: Model (adatok), View (interfész), Presenter (logika és állapot)
  • ViewContract — interfész, amelyen keresztül a Presenter kommunikál a View-val, biztosítva a tesztelhetőséget
  • Presenter — tartalmazza az összes üzleti logikát, nem függ az Android/iOS platform osztályoktól
  • Passive View — a View maximálisan passzív, csak a Presenter parancsaira jeleníti meg az adatokat
  • MVP vs MVC — a Presenter unit tesztekkel tesztelhető, az MVC-ben a Controller az UIKit/Android Framework-től függ

Mi az MVP: a Model-View-Presenter minta lényege

MVP (Model-View-Presenter) — architekturális minta, amelyet Martin Fowler javasolt a 2000-es évek elején az MVC evolúciójaként a felhasználói felület tesztelhetőségének javítására. A Model kezeli az adatokat és az üzleti logikát, a View felelős a megjelenítésért és a felhasználói bevitel feldolgozásáért, a Presenter — központi komponens, amely eseményeket fogad a View-tól, adatokat nyer ki a Model-ből, és állapotot képez a megjelenítéshez.

Az MVP fő különbsége az MVC-től — a Presenter nem rendelkezik közvetlen hivatkozással a View-ra. Ehelyett a Presenter a ViewContract interfészen keresztül kommunikál a View-val. A View implementálja ezt az interfészt, és átadja magát a Presenter-nek. Ez megszakítja a függőséget az UIKit-től (iOS) vagy az Android Framework-től — a Presenter elkülönítetten tesztelhető a ViewContract mock implementációjával. Az MVC-ben a controller UIViewController közvetlenül frissíti az UILabel-t, az MVP-ben a Presenter meghívja a view.showName(name) metódust, és a View dönti el, hogyan jelenítse meg.

KomponensFelelősségTesztelhetőség
ModelAdatok, üzleti logika, hálózati hívásokUnit tesztek (nem függ UI-tól)
ViewUI megjelenítése, események továbbítása a Presenter-nekMock implementáció interfészen keresztül
PresenterÜzleti logika, állapotkezelés, navigációUnit tesztek (ViewContract mock-on keresztül)

Az egyetlen felelősség elve az MVP-ben szigorúbban érvényesül, mint az MVC-ben: a View csak a renderelésért felelős, a Model — az adatokért, a Presenter — a logikáért és koordinációért. Valós projektekben a Presenter a képernyő kódjának 40–60%-át foglalja el, a View — 20–30%-át, a Model — 20–30%-át. Ez a felosztás lehetővé teszi a kulcsfontosságú üzleti logika tesztelését az Android emulátor vagy iOS szimulátor elindítása nélkül.

MVP Android-ban: Presenter, ViewContract és Activity

MVP Android-ban az Activity vagy Fragment View-ként működik, amely implementálja a ViewContract-ot — egy interfészt adatmegjelenítési metódusokkal. A Presenter az Activity-ben jön létre, feliratkoztatja a View-t magára, és kezeli az adatok betöltését. Képernyő elforgatásakor az Activity újra létrejön — a Presenter megőrizhető retain-fragment vagy külső tárolás segítségével, ami megoldja a tiszta MVC-re jellemző állapotvesztés problémáját.

kotlin
// ViewContract — interfész a Presenter és View közötti kommunikációhoz
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — tesztelhető logikai réteg
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 ?: "Ismeretlen hiba")
            }
        }
    }
}

// View (Activity) implementálja az interfészt
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) { /* a UI frissítése */ }
    override fun showLoading() { /* ProgressBar megjelenítése */ }
    override fun hideLoading() { /* ProgressBar elrejtése */ }
    override fun showError(message: String) { /* Snackbar megjelenítése */ }
}

Életciklus kezelés — az MVP fő problémája Android-on. Az Activity megsemmisül a képernyő elforgatásakor, és a presenter.attachView() újra meghívódik az onCreate()-ben. Ha az adatbetöltés aszinkron (RxJava, korutinok), a befejezés pillanatában a View már le lehet választva. Megoldás — a feliratkozások törlése a detachView()-ben vagy Loader használata a Support Library-ből (Jetpack nélküli projektekhez). Az IT Sectr-nél évekig használtuk az MVP + RxJava kombinációt kereskedelmi projektekben — a minta stabil, de fegyelmet igényel a feliratkozások kezelésében.

Retain-fragmentek — mechanizmus a Presenter megőrzésére képernyő elforgatásakor. A UI nélküli fragment (setRetainInstance(true)) tovább él, mint az Activity, és tárolja a Presenter-re mutató hivatkozást. Az Activity újralétrehozásakor a fragment ugyanazt a Presenter-t adja át az új Activity-nek. A retain-fragmentek elavultak az AndroidX óta, de a pre-Jetpack megfelelőjük (Fragment.setRetainInstance) még mindig működik legacy projektekben. A modern fejlesztésben a Google a ViewModel-t ajánlja a retain-fragmentek helyett.

MVP iOS-ben: Presenter és View Protocol

MVP iOS-ben a View protokollon keresztül épül fel. Az UIViewController implementálja a protokollt, a Presenter nem importálja az UIKit-et, és tisztán tesztelhető. Ellentétben az Apple MVC-vel, ahol az UIViewController maga tartalmazza a logikát és a közvetlen IBOutlet kapcsolatokat, a Presenter kezeli az állapotot és utasítja a View-t a protokoll metódusain keresztül. A View nem hoz döntéseket — végrehajtja a Presenter utasításait: showUser, showLoading, navigateToProfile.

swift
import Foundation

// View Protocol — absztrakció a Presenter számára
protocol UserViewProtocol: AnyObject {
    func showLoading()
    func hideLoading()
    func display(user: User)
    func displayError(message: String)
}

// Presenter — tiszta logika, UIKit nélkül
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) implementálja a protokollt
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
    }
    // ... a protokoll többi metódusa
}

Weak reference a View-ra — kötelező feltétel az iOS MVP-ben. Az UIViewController megsemmisülhet (pop a navigációs veremből), és a benne lévő closure a Presenter-ben retain cycle-t hoz létre. A gyenge hivatkozás (weak var) garantálja, hogy a View felszabadul, amikor elhagyja a képernyőt, függetlenül az aszinkron műveletek jelenlététől a Presenter-ben. Android-ban hasonló probléma a detachView()-on keresztül oldható meg — az onDestroy()-ben történő hívás nullázza a View-ra mutató hivatkozást.

Passive View vs Supervising Controller — az MVP két változata Martin Fowler-től. Passive View: a View nem tartalmaz logikát, a Presenter teljes mértékben kezeli az állapotot. Supervising Controller: a View maga végez egyszerű adatkötést (pl. data binding segítségével), a Presenter csak összetett forgatókönyvekbe avatkozik be. A mobilfejlesztésben a Passive View-t használják gyakrabban — maximális tesztelhetőséget és a képernyő állapotának kiszámíthatóságát biztosítja.

Az MVP és MVC közötti különbségek és a tesztelés előnyei

A fő különbség az MVP és MVC között — a View-val való kommunikáció módja. Az MVC-ben a Controller közvetlen hivatkozással rendelkezik a View-ra (UIViewController.IBOutlets, Activity.findViewById). Az MVP-ben a Presenter a ViewContract interfészen keresztül kommunikál a View-val. Ez a különbség gyökeresen megváltoztatja a tesztelhetőséget: egy Mock objektum, amely implementálja a ViewContract-ot, lehetővé teszi a Presenter logikájának ellenőrzését az alkalmazás, emulátor és UI keretrendszer elindítása nélkül.

SzempontMVCMVP
Kapcsolat a View-valKözvetlen (Controller → View)Interfészen keresztül (Presenter → ViewContract)
Logika teszteléseUIKit/Android Framework szükségesUnit tesztek platformfüggőségek nélkül
ÉletciklusController a képernyővel élPresenter tovább élhet (retain)
KomplexitásMinimális+1 interfész képernyőnként
Massive ControllerTipikus problémaLogika a Presenter-ben, View vékony

Presenter unit teszt példa Kotlin-ban: létrehozunk egy mock UserView-t, átadjuk a Presenter-nek, meghívjuk a loadUser-t, ellenőrizzük, hogy a showUser a megfelelő adatokkal lett-e meghívva. A teszt ezredmásodpercek alatt fut le, nem igényel emulátort. iOS-en hasonlóan — az OCMock vagy protokoll stub ellenőrzi a UserViewProtocol metódushívásait. Az IT Sectr MVP-s projektjeiben az üzleti logika unit teszt lefedettsége elérte a 85–90%-ot, ami 2–3-szor magasabb, mint a hasonló MVC projektekben.

Mikor előnyösebb az MVP az MVC-nél — szigorú stabilitási követelményekkel rendelkező projektekben: banki alkalmazások, orvosi rendszerek, fizetési terminálok. Ezekben a területeken a hiba ára magas, és a unit tesztek kritikusak. Post-MVP projektekben (amikor a termék már a piacon van, de a kódbázis legacy) az MVP lehetővé teszi a logika fokozatos kiemelését a Massive View Controller-ből egy tesztelhető rétegbe anélkül, hogy teljesen át kellene írni az architektúrát.

Az MVP korlátai és átmenet az MVVM-re

Az MVP fő hátrányai — az interfészek számának növekedése és a feliratkozások kézi kezelése. Minden képernyő legalább egy ViewContract + Presenter párost igényel, 50 képernyőhöz — 50 interfész és 50 Presenter osztály. Az MVVM-ben a ViewModel helyettesíti a Presenter-t, és reaktív mechanizmusokat használ (LiveData, StateFlow, ObservableObject), ami kiküszöböli a kézi attach/detach és a ViewContract interfészek szükségességét.

RxJava és MVP — népszerű kombináció Android 2015–2019. A Presenter feliratkozik az Observable-re a Repository-ból, és megjeleníti az eredményt a ViewContract-on keresztül. Probléma: a disposable-t explicit módon törölni kell a detachView()-ben, különben a feliratkozás szivárgása crash-t okoz a leválasztott View frissítésekor. Az RxLifecycle és AutoDispose könyvtárak részben automatizálták a törlést, de függőséget adtak hozzá. Az IT Sectr-nél 2020-ban váltottunk MVP+RxJava-ról MVVM+Flow-ra — a kód 25–30%-kal rövidebb lett a ViewContract eltűnésének köszönhetően.

Migráció MVP-ről MVVM-re — szakaszos folyamat. 1) A ViewContract cseréje LiveData/StateFlow-ra a Presenter-ben. 2) Az attach/detach metódusok eltávolítása — a feliratkozás az observe()-on keresztül történik. 3) A Presenter átnevezése ViewModel-re. 4) DI (Hilt/Koin) integrálása a ViewModelFactory-hoz. Egy képernyő migrációja 2–4 órát vesz igénybe, a teljes kódbázisé — 2–4 hetet egy 50–100 képernyős projekt esetén. A migráció után a ViewContract interfészek eltávolításra kerülnek, a kód rövidül, a tesztek megmaradnak.

Az MVP a modern fejlesztésben — a minta él, de alulmarad az MVVM-mel és MVI-vel szemben. A Google hivatalosan az MVVM-et ajánlja Jetpack-pel új projektekhez. Az Apple — az MVVM-et SwiftUI-val. Az MVP ismerete azonban kötelező a legacy kóddal való munkához: több száz Android alkalmazás a Google Play-ben még mindig MVP-n fut, beleértve a nagy bankok, kiskereskedők és szállítási vállalatok alkalmazásait. Az MVP megértése az alapja az MVI és Clean Architecture elsajátításának, mivel a Presenter a Use Case közvetlen elődje Robert Martin terminológiájában.

Gyakran Ismételt Kérdések

Miben különbözik az MVP az MVC-től?

Az MVP-ben a Presenter a ViewContract interfészen keresztül kommunikál a View-val, nem közvetlenül. Az MVC-ben a Controller közvetlen hivatkozással rendelkezik a View-ra az IBOutlet/findViewById segítségével. Az MVP lehetővé teszi a Presenter unit tesztekkel való tesztelését iOS Simulator vagy Android Emulator nélkül, mivel a Presenter nem függ az UIKit-től vagy Android Framework-től. Az MVC megköveteli az alkalmazás elindítását a controller teszteléséhez.

Mikor érdemes MVP-t használni az MVVM helyett?

Az MVP indokolt legacy projektekben, amelyek már erre a mintára épültek, és olyan alkalmazásokban, amelyek nem támogatják a reaktív mechanizmusokat (LiveData, StateFlow, Combine). Új projektekhez a Google az MVVM-et ajánlja Jetpack-pel (Android), az Apple pedig az MVVM-et SwiftUI-val (iOS). Az MVP továbbra is a legjobb választás tiszta UIKit-es projektekhez Combine nélkül, amikor az üzleti logika unit tesztelése szükséges.

Hogyan oldható meg a Presenter elvesztésének problémája képernyő elforgatásakor?

Android-on — használjunk retain-fragmentet (setRetainInstance(true)) vagy ViewModel-t a Jetpack-ből. A retain-fragment megőrzi a Presenter-t elforgatáskor, és átadja az új Activity-nek. A Google ViewModel-je — modern alternatíva, amely automatikusan megőrzi az állapotot elforgatáskor retain-fragmentek nélkül. iOS-en — a Presenter minden viewDidLoad-kor újra létrejön, de egy külön koordinátor szolgáltatásban cache-elődik.

Hány osztály szükséges egy képernyőhöz az MVP-ben?

Minimum 4: a ViewContract interfész, a ViewContract implementációja (Activity/Fragment), Presenter, Model (Repository). Ha Dagger/Hilt-et használunk, egy DI modul is hozzáadódik. 50 képernyőhöz ez 200+ osztály. Az MVVM képernyőnként 1 fájllal csökkenti a számot (ViewContract nem szükséges), az MVI State és Intent osztályokat ad hozzá. Az osztályok száma — a fő érv az MVP ellen nagy projektekben.

Mi a különbség a Passive View és a Supervising Controller között az MVP-ben?

Passive View — a View nem tartalmaz logikát, a Presenter teljes mértékben kezeli az állapotot és az adatokat. Supervising Controller — a View maga végzi az egyszerű kötést (data binding), a Presenter összetett forgatókönyvekbe avatkozik be. A mobilfejlesztésben a Passive View dominál — maximális tesztelhetőséget és kiszámíthatóságot biztosít. A Supervising Controller-t webes keretrendszerekben (ASP.NET Web Forms, GWT) használják.

Összefoglalás

  • MVP (Model-View-Presenter) — az MVC evolúciója tesztelhető Presenter réteggel a ViewContract interfészen keresztül
  • ViewContract — interfész, amely elvonatkoztatja a View-t a Presenter-től, lehetővé téve a mock tesztelést
  • Passive View — az MVP domináns változata a mobilfejlesztésben passzív View-val
  • Presenter — tartalmazza az üzleti logikát, nem függ az UIKit-től vagy Android Framework-től
  • MVP vs MVC — az MVP megoldja a tesztelési problémát, de hozzáad 1 interfészt képernyőnként
  • Android retain-fragmentek — a Presenter megőrzése képernyő elforgatásakor a Jetpack ViewModel megjelenése előtt
  • Migráció MVVM-re — a ViewContract cseréje LiveData/StateFlow-ra 25–30%-kal rövidíti a kódot

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is