MVP — co to je, vzor Model-View-Presenter v iOS a Android

Autor: IT Sectr Publikováno: 2026-02-16 Doba čtení: 10 min

MVP (Model-View-Presenter) — architektonický vzor, ve kterém Presenter funguje jako prostředník mezi Model a View prostřednictvím rozhraní ViewContract. Na rozdíl od MVC, kde Controller přímo řídí View přes UIKit, Presenter není závislý na frameworku — pracuje přes abstrakci, což ho činí testovatelným bez Android SDK nebo UIKit. MVP byl široce používán ve vývoji pro Android před příchodem Jetpack a zůstává relevantní pro legacy projekty. Více v článku Martina Fowlera.

Hlavní body

  • MVP — tři komponenty: Model (data), View (rozhraní), Presenter (logika a stav)
  • ViewContract — rozhraní, přes které Presenter komunikuje s View, zajišťující testovatelnost
  • Presenter — obsahuje veškerou business logiku, není závislý na platformních třídách Android/iOS
  • Passive View — View je maximálně pasivní, pouze zobrazuje data na příkazy Presenteru
  • MVP vs MVC — Presenter je testován unit testy, Controller v MVC závisí na UIKit/Android Framework

Co je MVP: podstata vzoru Model-View-Presenter

MVP (Model-View-Presenter) — architektonický vzor navržený Martinem Fowlerem na počátku 2000. let jako evoluce MVC pro zlepšení testovatelnosti uživatelského rozhraní. Model spravuje data a business logiku, View je odpovědné za zobrazení a zpracování vstupu uživatele, Presenter — centrální komponenta, která přijímá události z View, získává data z Model a vytváří stav pro zobrazení.

Hlavní rozdíl mezi MVP a MVC — Presenter nemá přímý odkaz na View. Místo toho Presenter komunikuje s View prostřednictvím rozhraní ViewContract. View toto rozhraní implementuje a předává se Presenteru. To přerušuje závislost na UIKit (iOS) nebo Android Framework — Presenter je testován izolovaně s mock implementací ViewContract. V MVC controller UIViewController přímo aktualizuje UILabel, v MVP Presenter volá metodu view.showName(name) a View rozhoduje, jak zobrazit.

KomponentaOdpovědnostTestovatelnost
ModelData, business logika, síťová voláníUnit testy (nezávisí na UI)
ViewZobrazení UI, předávání událostí PresenteruMock implementace přes rozhraní
PresenterBusiness logika, správa stavu, navigaceUnit testy (přes mock ViewContract)

Princip jedné odpovědnosti je v MVP dodržován přísněji než v MVC: View je odpovědné pouze za vykreslování, Model — za data, Presenter — za logiku a koordinaci. V reálných projektech zabírá Presenter 40–60% kódu obrazovky, View — 20–30%, Model — 20–30%. Toto rozdělení umožňuje testovat klíčovou business logiku bez spouštění Android emulátoru nebo iOS simulátoru.

MVP v Android: Presenter, ViewContract a Activity

MVP v Android používá Activity nebo Fragment jako View, které implementuje ViewContract — rozhraní s metodami pro zobrazení dat. Presenter je vytvořen v Activity, připojí View k sobě a spravuje načítání dat. Při otočení obrazovky je Activity znovu vytvořeno — Presenter může být zachován přes retain-fragment nebo externí úložiště, což řeší problém ztráty stavu typický pro čisté MVC.

kotlin
// ViewContract — rozhraní pro komunikaci Presenteru s View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — testovatelná vrstva logiky
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 ?: "Neznámá chyba")
            }
        }
    }
}

// View (Activity) implementuje rozhraní
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) { /* aktualizovat UI */ }
    override fun showLoading() { /* zobrazit ProgressBar */ }
    override fun hideLoading() { /* skrýt ProgressBar */ }
    override fun showError(message: String) { /* zobrazit Snackbar */ }
}

Správa životního cyklu — klíčový problém MVP na Android. Activity je zničeno při otočení obrazovky a presenter.attachView() je znovu voláno v onCreate(). Pokud je načítání dat asynchronní (RxJava, korutiny), v okamžiku dokončení může být View odpojeno. Řešení — zrušení odběrů v detachView() nebo použití Loader z Support Library (pro projekty bez Jetpack). V IT Sectr jsme léta používali kombinaci MVP + RxJava v komerčních projektech — vzor je stabilní, ale vyžaduje disciplínu ve správě odběrů.

Retain-fragmenty — mechanismus pro zachování Presenter při otočení obrazovky. Fragment bez UI (setRetainInstance(true)) žije déle než Activity a uchovává odkaz na Presenter. Při znovuvytvoření Activity fragment předává stejný Presenter nové Activity. Retain-fragmenty jsou od AndroidX zastaralé, ale jejich pre-Jetpack ekvivalent (Fragment.setRetainInstance) stále funguje v legacy projektech. V moderním vývoji Google doporučuje ViewModel místo retain-fragmentů.

MVP v iOS: Presenter a View Protocol

MVP v iOS je postaven přes protokol View. UIViewController implementuje protokol, Presenter neimportuje UIKit a je čistě testovatelný. Na rozdíl od Apple MVC, kde UIViewController sám obsahuje logiku a přímá IBOutlet připojení, Presenter spravuje stav a příkazuje View přes metody protokolu. View nerozhoduje — vykonává příkazy Presenteru: showUser, showLoading, navigateToProfile.

swift
import Foundation

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

// Presenter — čistá logika, bez 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) implementuje protokol
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
    }
    // ... zbývající metody protokolu
}

Weak reference na View — povinná podmínka v iOS MVP. UIViewController může být zničen (pop z navigačního zásobníku) a jeho closure v Presenteru vytvoří retain cycle. Slabý odkaz (weak var) zaručuje, že View je uvolněno, když opustí obrazovku, bez ohledu na přítomnost asynchronních operací v Presenteru. V Android je podobný problém řešen přes detachView() — volání v onDestroy() vynuluje odkaz na View.

Passive View vs Supervising Controller — dvě varianty MVP od Martina Fowlera. Passive View: View neobsahuje logiku, Presenter plně spravuje stav. Supervising Controller: View samo provádí jednoduché vázání dat (např. přes data binding), Presenter zasahuje pouze do složitých scénářů. V mobilním vývoji je Passive View používáno častěji — poskytuje maximální testovatelnost a předvídatelnost stavu obrazovky.

Rozdíly mezi MVP a MVC a výhody testování

Hlavní rozdíl mezi MVP a MVC — způsob komunikace s View. V MVC má Controller přímý odkaz na View (UIViewController.IBOutlets, Activity.findViewById). V MVP Presenter komunikuje s View prostřednictvím rozhraní ViewContract. Tento rozdíl radikálně mění testovatelnost: Mock objekt implementující ViewContract umožňuje kontrolu logiky Presenteru bez spuštění aplikace, emulátoru a UI frameworku.

KritériumMVCMVP
Spojení s ViewPřímé (Controller → View)Přes rozhraní (Presenter → ViewContract)
Testování logikyVyžaduje UIKit/Android FrameworkUnit testy bez platformních závislostí
Životní cyklusController žije s obrazovkouPresenter může žít déle (retain)
SložitostMinimální+1 rozhraní na obrazovku
Massive ControllerTypický problémLogika v Presenteru, View tenké

Příklad unit testu Presenter v Kotlin: vytvoří se mock UserView, předá se Presenteru, zavolá se loadUser, zkontroluje se, zda byl showUser zavolán se správnými daty. Test se provede v milisekundách, nevyžaduje emulátor. Na iOS podobně — OCMock nebo stub protokolu kontroluje volání metod UserViewProtocol. V projektech IT Sectr s MVP dosahovalo pokrytí business logiky unit testy 85–90%, což je 2–3krát více než v podobných MVC projektech.

Kdy je MVP výhodnější než MVC — v projektech s přísnými požadavky na stabilitu: bankovní aplikace, lékařské systémy, platební terminály. V těchto oblastech je cena chyby vysoká a unit testy jsou kritické. V Post-MVP projektech (když je produkt již na trhu, ale kódová základna je legacy) MVP umožňuje postupně přesouvat logiku z Massive View Controller do testovatelné vrstvy bez úplného přepisování architektury.

Omezení MVP a přechod na MVVM

Hlavní nevýhody MVP — nárůst počtu rozhraní a ruční správa odběrů. Každá obrazovka vyžaduje alespoň jeden ViewContract + Presenter, pro 50 obrazovek — 50 rozhraní a 50 tříd Presenter. V MVVM ViewModel nahrazuje Presenter a používá reaktivní mechanismy (LiveData, StateFlow, ObservableObject), což eliminuje potřebu ručního attach/detach a rozhraní ViewContract.

RxJava a MVP — populární kombinace v Android 2015–2019. Presenter se odebírá Observable z Repository, zobrazuje výsledek přes ViewContract. Problém: disposable musí být explicitně zrušen v detachView(), jinak únik odběru způsobí crash při aktualizaci odpojeného View. Knihovny RxLifecycle a AutoDispose částečně automatizovaly rušení, ale přidaly závislost. V IT Sectr jsme v roce 2020 přešli z MVP+RxJava na MVVM+Flow — kód se zkrátil o 25–30% díky odstranění ViewContract.

Migrace z MVP na MVVM — postupný proces. 1) Nahradit ViewContract LiveData/StateFlow v Presenteru. 2) Odstranit metody attach/detach — odběr probíhá přes observe(). 3) Přejmenovat Presenter na ViewModel. 4) Integrovat DI (Hilt/Koin) pro ViewModelFactory. Migrace jedné obrazovky trvá 2–4 hodiny, celé kódové základny — 2–4 týdny pro projekt s 50–100 obrazovkami. Po migraci jsou rozhraní ViewContract odstraněna, kód se zkracuje, testy zůstávají.

MVP v moderním vývoji — vzor žije, ale ustupuje MVVM a MVI. Google oficiálně doporučuje MVVM s Jetpack pro nové projekty. Apple — MVVM se SwiftUI. Znalost MVP je však povinná pro práci s legacy kódem: stovky Android aplikací v Google Play stále běží na MVP, včetně aplikací velkých bank, maloobchodníků a dopravních společností. Porozumění MVP je základem pro zvládnutí MVI a Clean Architecture, protože Presenter je přímým předchůdcem Use Case v terminologii Roberta Martina.

Často kladené otázky

Čím se liší MVP od MVC?

V MVP Presenter komunikuje s View prostřednictvím rozhraní ViewContract, ne přímo. V MVC má Controller přímý odkaz na View přes IBOutlet/findViewById. MVP umožňuje testovat Presenter unit testy bez iOS Simulator nebo Android Emulator, protože Presenter není závislý na UIKit nebo Android Framework. MVC vyžaduje spuštění aplikace pro testování controlleru.

Kdy použít MVP místo MVVM?

MVP je opodstatněný v legacy projektech již postavených na tomto vzoru a v aplikacích bez podpory reaktivních mechanismů (LiveData, StateFlow, Combine). Pro nové projekty Google doporučuje MVVM s Jetpack (Android) a Apple doporučuje MVVM se SwiftUI (iOS). MVP zůstává nejlepší volbou pro projekty na čistém UIKit bez Combine při požadavku na unit testování business logiky.

Jak vyřešit problém ztráty Presenter při otočení obrazovky?

Na Android — použít retain-fragment (setRetainInstance(true)) nebo ViewModel z Jetpack. Retain-fragment uchovává Presenter při otočení a předává ho nové Activity. ViewModel od Google — moderní alternativa, která automaticky uchovává stav při otočení bez retain-fragmentů. Na iOS — Presenter je vytvořen znovu při každém viewDidLoad, ale je cacheován v samostatné koordinační službě.

Kolik tříd je potřeba pro jednu obrazovku v MVP?

Minimum 4: rozhraní ViewContract, implementace ViewContract (Activity/Fragment), Presenter, Model (Repository). Pokud se používají Dagger/Hilt, přidá se DI modul. Pro 50 obrazovek je to 200+ tříd. MVVM snižuje počet o 1 soubor na obrazovku (ViewContract není potřeba), MVI přidává třídy State a Intent. Počet tříd — hlavní argument proti MVP ve velkých projektech.

Jaký je rozdíl mezi Passive View a Supervising Controller v MVP?

Passive View — View neobsahuje logiku, Presenter plně spravuje stav a data. Supervising Controller — View samo provádí jednoduché vázání (data binding), Presenter zasahuje do složitých scénářů. V mobilním vývoji dominuje Passive View — poskytuje maximální testovatelnost a předvídatelnost. Supervising Controller se používá ve webových frameworkách (ASP.NET Web Forms, GWT).

Shrnutí

  • MVP (Model-View-Presenter) — evoluce MVC s testovatelnou vrstvou Presenter přes rozhraní ViewContract
  • ViewContract — rozhraní abstrahující View od Presenteru, umožňující mock testování
  • Passive View — dominantní varianta MVP v mobilním vývoji s pasivním View
  • Presenter — obsahuje business logiku, nezávisí na UIKit nebo Android Framework
  • MVP vs MVC — MVP řeší problém testování, ale přidává 1 rozhraní na obrazovku
  • Android retain-fragmenty — uchování Presenter při otočení obrazovky před příchodem Jetpack ViewModel
  • Migrace na MVVM — nahrazení ViewContract LiveData/StateFlow zkracuje kód o 25–30%

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také