MVP (Model-View-Presenter) — arkitiktural na pattern kung saan ang Presenter ay nagsisilbing tagapamagitan sa pagitan ng Model at View sa pamamagitan ng interface ng ViewContract. Hindi tulad ng MVC, kung saan ang Controller ay direktang namamahala ng View sa pamamagitan ng UIKit, ang Presenter ay hindi nakadepende sa framework — ito ay gumagana sa pamamagitan ng abstraction, na ginagawang masusubok nang walang Android SDK o UIKit. Ang MVP ay malawakang ginagamit sa Android development bago ang Jetpack at nananatiling may kaugnayan para sa legacy na mga proyekto. Higit pa sa artikulo ni Martin Fowler.
Mga Pangunahing Punto
MVP (Model-View-Presenter) — arkitiktural na pattern na iminungkahi ni Martin Fowler noong unang bahagi ng 2000s bilang ebolusyon ng MVC para sa pagpapabuti ng testability ng user interface. Ang Model ay namamahala ng datos at business logic, ang View ay responsable para sa pagpapakita at pagproseso ng input ng user, ang Presenter — sentral na component na tumatanggap ng mga event mula sa View, kumukuha ng datos mula sa Model, at bumubuo ng estado para sa pagpapakita.
Pangunahing pagkakaiba ng MVP at MVC — ang Presenter ay walang direktang referens sa View. Sa halip, ang Presenter ay nakikipag-ugnayan sa View sa pamamagitan ng interface ng ViewContract. Ang View ay nagpapatupad ng interface na ito at ipinapasa ang sarili nito sa Presenter. Pinutol nito ang dependency sa UIKit (iOS) o Android Framework — ang Presenter ay sinusuri nang hiwalay gamit ang mock implementation ng ViewContract. Sa MVC, ang controller UIViewController ay direktang nag-a-update ng UILabel, sa MVP ang Presenter ay tumatawag ng method na view.showName(name), at ang View ang nagdedesisyon kung paano ipapakita.
| Component | Responsibilidad | Testability |
|---|---|---|
| Model | Datos, business logic, network calls | Unit test (hindi nakadepende sa UI) |
| View | Pagpapakita ng UI, pagpapasa ng event sa Presenter | Mock implementation sa pamamagitan ng interface |
| Presenter | Business logic, pamamahala ng estado, nabigasyon | Unit test (sa pamamagitan ng mock ViewContract) |
Prinsipyo ng iisang responsibilidad sa MVP ay mas mahigpit na sinusunod kaysa sa MVC: ang View ay responsable lamang para sa rendering, Model — para sa datos, Presenter — para sa lohika at koordinasyon. Sa totoong proyekto, ang Presenter ay sumasakop ng 40–60% ng code ng screen, View — 20–30%, Model — 20–30%. Ang ganitong paghahati ay nagpapahintulot sa pagsubok ng pangunahing business logic nang hindi pinapatakbo ang Android emulator o iOS simulator.
MVP sa Android ay gumagamit ng Activity o Fragment bilang View na nagpapatupad ng ViewContract — interface na may mga method para sa pagpapakita ng datos. Ang Presenter ay nilikha sa Activity, ikinokonekta ang View sa sarili nito, at namamahala ng pag-load ng datos. Sa pag-ikot ng screen, ang Activity ay muling nilikha — ang Presenter ay maaaring mapanatili sa pamamagitan ng retain-fragment o panlabas na imbakan, na lumulutas sa problema ng pagkawala ng estado na katangian ng purong MVC.
// ViewContract — interface para sa komunikasyon ng Presenter sa View
interface UserView {
fun showLoading()
fun hideLoading()
fun showUser(user: User)
fun showError(message: String)
}
// Presenter — masusubok na layer ng lohika
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 ?: "Hindi kilalang error")
}
}
}
}
// View (Activity) ay nagpapatupad ng 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) { /* i-update ang UI */ }
override fun showLoading() { /* ipakita ang ProgressBar */ }
override fun hideLoading() { /* itago ang ProgressBar */ }
override fun showError(message: String) { /* ipakita ang Snackbar */ }
}
Pamamahala ng lifecycle — pangunahing problema ng MVP sa Android. Ang Activity ay nasisira sa pag-ikot ng screen, at ang presenter.attachView() ay tinatawag muli sa onCreate(). Kung ang pag-load ng datos ay asynchronous (RxJava, coroutine), sa oras ng pagkumpleto ang View ay maaaring naka-detach. Solusyon — kanselahin ang mga subscription sa detachView() o gumamit ng Loader mula sa Support Library (para sa mga proyektong walang Jetpack). Sa IT Sectr, ginamit namin ang kombinasyon ng MVP + RxJava sa loob ng maraming taon sa komersyal na proyekto — ang pattern ay matatag, ngunit nangangailangan ng disiplina sa pamamahala ng subscription.
Retain-fragment — mekanismo para mapanatili ang Presenter sa pag-ikot ng screen. Fragment na walang UI (setRetainInstance(true)) ay nabubuhay nang mas mahaba kaysa Activity at nag-iimbak ng referens sa Presenter. Sa muling paglikha ng Activity, ang fragment ay nagpapasa ng parehong Presenter sa bagong Activity. Ang retain-fragment ay deprecated mula noong AndroidX, ngunit ang pre-Jetpack na katumbas nito (Fragment.setRetainInstance) ay gumagana pa rin sa legacy na proyekto. Sa modernong development, inirerekomenda ng Google ang ViewModel sa halip na retain-fragment.
MVP sa iOS ay binuo sa pamamagitan ng View protocol. Ang UIViewController ay nagpapatupad ng protocol, ang Presenter ay hindi nag-i-import ng UIKit at puro sinusuri. Hindi tulad ng Apple MVC, kung saan ang UIViewController mismo ay naglalaman ng lohika at direktang koneksyon ng IBOutlet, ang Presenter ay namamahala ng estado at nag-uutos sa View sa pamamagitan ng mga method ng protocol. Ang View ay hindi gumagawa ng desisyon — ito ay nagpapatupad ng mga utos ng Presenter: showUser, showLoading, navigateToProfile.
import Foundation
// View Protocol — abstraction para sa Presenter
protocol UserViewProtocol: AnyObject {
func showLoading()
func hideLoading()
func display(user: User)
func displayError(message: String)
}
// Presenter — purong lohika, walang 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) ay nagpapatupad ng protocol
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
}
// ... natitirang mga method ng protocol
}
Weak reference sa View — sapilitang kondisyon sa iOS MVP. Ang UIViewController ay maaaring masira (pop mula sa navigation stack), at ang closure nito sa Presenter ay lilikha ng retain cycle. Ang mahinang referens (weak var) ay ginagarantiyahan na ang View ay ilalabas kapag umalis ito sa screen, anuman ang pagkakaroon ng asynchronous na operasyon sa Presenter. Sa Android, ang katulad na problema ay nalulutas sa pamamagitan ng detachView() — tawag sa onDestroy() ay nagpapawalang-bisa sa referens sa View.
Passive View vs Supervising Controller — dalawang variant ng MVP mula kay Martin Fowler. Passive View: ang View ay walang lohika, ang Presenter ay ganap na namamahala ng estado. Supervising Controller: ang View mismo ay gumagawa ng simpleng data binding (halimbawa, sa pamamagitan ng data binding), ang Presenter ay nakikialam lamang sa mga komplikadong senaryo. Sa mobile development, ang Passive View ay mas madalas na ginagamit — nagbibigay ito ng maximum na testability at predictability ng estado ng screen.
Pangunahing pagkakaiba ng MVP at MVC — paraan ng komunikasyon sa View. Sa MVC, ang Controller ay may direktang referens sa View (UIViewController.IBOutlets, Activity.findViewById). Sa MVP, ang Presenter ay nakikipag-ugnayan sa View sa pamamagitan ng interface ng ViewContract. Ang pagkakaibang ito ay radikal na nagbabago ng testability: isang Mock object na nagpapatupad ng ViewContract ay nagpapahintulot sa pagsusuri ng lohika ng Presenter nang hindi pinapatakbo ang application, emulator, at UI framework.
| Kriteria | MVC | MVP |
|---|---|---|
| Koneksyon sa View | Direkta (Controller → View) | Sa pamamagitan ng interface (Presenter → ViewContract) |
| Pagsubok ng lohika | Nangangailangan ng UIKit/Android Framework | Unit test nang walang platform dependencies |
| Lifecycle | Controller nabubuhay kasama ng screen | Presenter maaaring mabuhay nang mas mahaba (retain) |
| Kompleksidad | Minimal | +1 interface bawat screen |
| Massive Controller | Karaniwang problema | Lohika sa Presenter, View manipis |
Halimbawa ng unit test ng Presenter sa Kotlin: gumawa ng mock UserView, ipasa sa Presenter, tawagan ang loadUser, suriin kung ang showUser ay tinawag na may tamang datos. Ang test ay tatakbo sa millisecond, hindi nangangailangan ng emulator. Sa iOS katulad — OCMock o protocol stub ay sumusuri ng mga tawag sa method ng UserViewProtocol. Sa mga proyekto ng IT Sectr na may MVP, ang coverage ng business logic ng unit test ay umabot ng 85–90%, 2–3 beses na mas mataas kaysa sa mga katulad na MVC proyekto.
Kailan mas gusto ang MVP kaysa MVC — sa mga proyektong may mahigpit na pangangailangan sa katatagan: banking applications, medical system, payment terminals. Sa mga larangang ito, mataas ang halaga ng error at kritikal ang unit test. Sa Post-MVP na proyekto (kapag ang produkto ay nasa merkado na ngunit ang codebase ay legacy), pinapayagan ng MVP ang unti-unting paglipat ng lohika mula sa Massive View Controller patungo sa masusubok na layer nang hindi ganap na isinusulat muli ang arkitektura.
Pangunahing disbentaha ng MVP — pagtaas ng bilang ng mga interface at manu-manong pamamahala ng subscription. Bawat screen ay nangangailangan ng kahit isang ViewContract + Presenter, para sa 50 screen — 50 interface at 50 Presenter na klase. Sa MVVM, ang ViewModel ay pumapalit sa Presenter at gumagamit ng reaktibong mekanismo (LiveData, StateFlow, ObservableObject), na nag-aalis ng pangangailangan para sa manu-manong attach/detach at mga interface ng ViewContract.
RxJava at MVP — sikat na kombinasyon sa Android 2015–2019. Ang Presenter ay nagse-subscribe sa Observable mula sa Repository, nagpapakita ng resulta sa pamamagitan ng ViewContract. Problema: ang disposable ay dapat na tahasang kanselahin sa detachView(), kung hindi ang pagtagas ng subscription ay magdudulot ng crash kapag ina-update ang naka-detach na View. Ang mga library na RxLifecycle at AutoDispose ay bahagyang nag-automate ng pagkansela, ngunit nagdagdag ng dependency. Sa IT Sectr, lumipat kami mula MVP+RxJava patungo sa MVVM+Flow noong 2020 — ang code ay naging mas maikli ng 25–30% dahil sa pagkawala ng ViewContract.
Migrasyon mula MVP patungo sa MVVM — proseso nang paunti-unti. 1) Palitan ang ViewContract ng LiveData/StateFlow sa Presenter. 2) Alisin ang mga method na attach/detach — ang subscription ay sa pamamagitan ng observe(). 3) Palitan ang pangalan ng Presenter sa ViewModel. 4) Isama ang DI (Hilt/Koin) para sa ViewModelFactory. Ang migrasyon ng isang screen ay tumatagal ng 2–4 na oras, ng buong codebase — 2–4 na linggo para sa proyekto na may 50–100 screen. Pagkatapos ng migrasyon, ang mga interface ng ViewContract ay tinatanggal, ang code ay umiikli, ang mga test ay nananatili.
MVP sa modernong development — ang pattern ay buhay pa, ngunit natatalo sa MVVM at MVI. Opisyal na inirerekomenda ng Google ang MVVM na may Jetpack para sa bagong proyekto. Apple — MVVM na may SwiftUI. Gayunpaman, ang kaalaman sa MVP ay sapilitan para sa pagtatrabaho sa legacy code: daan-daang Android application sa Google Play ay tumatakbo pa rin sa MVP, kabilang ang mga application ng malalaking bangko, retailer, at transportasyon. Ang pag-unawa sa MVP ay pundasyon para sa pag-master ng MVI at Clean Architecture, dahil ang Presenter ay direktang ninuno ng Use Case sa terminolohiya ni Robert Martin.
Mga Madalas Itanong
Sa MVP, ang Presenter ay nakikipag-ugnayan sa View sa pamamagitan ng interface ng ViewContract, hindi direkta. Sa MVC, ang Controller ay may direktang referens sa View sa pamamagitan ng IBOutlet/findViewById. Pinapayagan ng MVP ang pagsubok ng Presenter gamit ang unit test nang walang iOS Simulator o Android Emulator, dahil hindi nakadepende ang Presenter sa UIKit o Android Framework. Ang MVC ay nangangailangan ng pagpapatakbo ng application para sa pagsubok ng controller.
Ang MVP ay makatwiran sa legacy na proyekto na binuo na sa pattern na ito at sa mga application na walang suporta para sa reaktibong mekanismo (LiveData, StateFlow, Combine). Para sa bagong proyekto, inirerekomenda ng Google ang MVVM na may Jetpack (Android) at inirerekomenda ng Apple ang MVVM na may SwiftUI (iOS). Ang MVP ay nananatiling pinakamahusay na pagpipilian para sa mga proyekto sa purong UIKit na walang Combine kapag kinakailangan ang unit testing ng business logic.
Sa Android — gumamit ng retain-fragment (setRetainInstance(true)) o ViewModel mula sa Jetpack. Ang retain-fragment ay nag-iimbak ng Presenter sa pag-ikot at ipinapasa ito sa bagong Activity. Ang ViewModel mula sa Google — modernong alternatibo na awtomatikong nagpapanatili ng estado sa pag-ikot nang walang retain-fragment. Sa iOS — ang Presenter ay nilikha muli sa bawat viewDidLoad, ngunit na-cache sa hiwalay na coordinator service.
Minimum 4: interface ng ViewContract, implementasyon ng ViewContract (Activity/Fragment), Presenter, Model (Repository). Kung gumagamit ng Dagger/Hilt, magdagdag ng DI module. Para sa 50 screen, 200+ klase. Binabawasan ng MVVM ang bilang ng 1 file bawat screen (hindi kailangan ang ViewContract), nagdaragdag ng State at Intent na klase ang MVI. Ang bilang ng klase — pangunahing argumento laban sa MVP sa malalaking proyekto.
Passive View — ang View ay walang lohika, ang Presenter ay ganap na namamahala ng estado at datos. Supervising Controller — ang View mismo ay gumagawa ng simpleng binding (data binding), ang Presenter ay nakikialam sa mga komplikadong senaryo. Sa mobile development, nananaig ang Passive View — nagbibigay ng maximum na testability at predictability. Ang Supervising Controller ay ginagamit sa web frameworks (ASP.NET Web Forms, GWT).
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din