MVP (Model-View-Presenter) — pola arsitektur di mana Presenter bertindak sebagai perantara antara Model dan View melalui antarmuka ViewContract. Berbeda dengan MVC, di mana Controller langsung mengelola View melalui UIKit, Presenter tidak bergantung pada framework — ia bekerja melalui abstraksi, yang membuatnya dapat diuji tanpa Android SDK atau UIKit. MVP banyak digunakan dalam pengembangan Android sebelum munculnya Jetpack dan tetap relevan untuk proyek legacy. Selengkapnya di artikel Martin Fowler.
Poin Utama
MVP (Model-View-Presenter) — pola arsitektur yang diusulkan oleh Martin Fowler di awal tahun 2000-an sebagai evolusi dari MVC untuk meningkatkan testabilitas antarmuka pengguna. Model mengelola data dan logika bisnis, View bertanggung jawab untuk menampilkan dan memproses masukan pengguna, Presenter — komponen pusat yang menerima peristiwa dari View, mengambil data dari Model, dan membentuk status untuk ditampilkan.
Perbedaan utama MVP dari MVC — Presenter tidak memiliki referensi langsung ke View. Sebaliknya, Presenter berinteraksi dengan View melalui antarmuka ViewContract. View mengimplementasikan antarmuka ini dan menyerahkan dirinya ke Presenter. Ini memutus ketergantungan pada UIKit (iOS) atau Android Framework — Presenter diuji secara terisolasi dengan implementasi mock dari ViewContract. Di MVC, controller UIViewController langsung memperbarui UILabel, di MVP Presenter memanggil metode view.showName(name), dan View memutuskan bagaimana menampilkan.
| Komponen | Tanggung Jawab | Testabilitas |
|---|---|---|
| Model | Data, logika bisnis, panggilan jaringan | Unit test (tidak bergantung pada UI) |
| View | Menampilkan UI, meneruskan peristiwa ke Presenter | Implementasi mock melalui antarmuka |
| Presenter | Logika bisnis, manajemen status, navigasi | Unit test (melalui mock ViewContract) |
Prinsip tanggung jawab tunggal di MVP dipatuhi lebih ketat daripada di MVC: View hanya bertanggung jawab untuk rendering, Model — untuk data, Presenter — untuk logika dan koordinasi. Di proyek nyata, Presenter menempati 40–60% kode layar, View — 20–30%, Model — 20–30%. Pembagian ini memungkinkan pengujian logika bisnis kunci tanpa menjalankan emulator Android atau simulator iOS.
MVP di Android menggunakan Activity atau Fragment sebagai View yang mengimplementasikan ViewContract — antarmuka dengan metode untuk menampilkan data. Presenter dibuat di Activity, menghubungkan View ke dirinya, dan mengelola pemuatan data. Saat rotasi layar, Activity dibuat ulang — Presenter dapat dipertahankan melalui retain-fragment atau penyimpanan eksternal, yang memecahkan masalah kehilangan status yang khas pada MVC murni.
// ViewContract — antarmuka untuk komunikasi Presenter dengan View
interface UserView {
fun showLoading()
fun hideLoading()
fun showUser(user: User)
fun showError(message: String)
}
// Presenter — lapisan logika yang dapat diuji
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 ?: "Kesalahan tidak diketahui")
}
}
}
}
// View (Activity) mengimplementasikan antarmuka
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) { /* perbarui UI */ }
override fun showLoading() { /* tampilkan ProgressBar */ }
override fun hideLoading() { /* sembunyikan ProgressBar */ }
override fun showError(message: String) { /* tampilkan Snackbar */ }
}
Manajemen siklus hidup — masalah utama MVP di Android. Activity dihancurkan saat rotasi layar, dan presenter.attachView() dipanggil kembali di onCreate(). Jika pemuatan data bersifat asinkron (RxJava, coroutine), pada saat selesai View mungkin sudah terlepas. Solusi — membatalkan langganan di detachView() atau menggunakan Loader dari Support Library (untuk proyek tanpa Jetpack). Di IT Sectr, kami bertahun-tahun menggunakan kombinasi MVP + RxJava di proyek komersial — pola ini stabil, tetapi membutuhkan disiplin dalam manajemen langganan.
Retain-fragment — mekanisme untuk mempertahankan Presenter saat rotasi layar. Fragment tanpa UI (setRetainInstance(true)) hidup lebih lama dari Activity dan menyimpan referensi ke Presenter. Saat Activity dibuat ulang, fragment meneruskan Presenter yang sama ke Activity baru. Retain-fragment sudah tidak digunakan lagi sejak AndroidX, tetapi padanan pre-Jetpack-nya (Fragment.setRetainInstance) masih berfungsi di proyek legacy. Dalam pengembangan modern, Google merekomendasikan ViewModel sebagai pengganti retain-fragment.
MVP di iOS dibangun melalui protokol View. UIViewController mengimplementasikan protokol, Presenter tidak mengimpor UIKit dan diuji secara murni. Berbeda dengan Apple MVC, di mana UIViewController sendiri berisi logika dan koneksi IBOutlet langsung, Presenter mengelola status dan memerintahkan View melalui metode protokol. View tidak membuat keputusan — ia menjalankan perintah Presenter: showUser, showLoading, navigateToProfile.
import Foundation
// View Protocol — abstraksi untuk Presenter
protocol UserViewProtocol: AnyObject {
func showLoading()
func hideLoading()
func display(user: User)
func displayError(message: String)
}
// Presenter — logika murni, tanpa 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) mengimplementasikan 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
}
// ... sisa metode protokol
}
Weak reference ke View — kondisi wajib di iOS MVP. UIViewController dapat dihancurkan (pop dari tumpukan navigasi), dan closure-nya di Presenter akan membuat retain cycle. Referensi lemah (weak var) memastikan bahwa View dibebaskan saat meninggalkan layar, terlepas dari adanya operasi asinkron di Presenter. Di Android, masalah serupa diselesaikan melalui detachView() — panggilan di onDestroy() menghilangkan referensi ke View.
Passive View vs Supervising Controller — dua varian MVP dari Martin Fowler. Passive View: View tidak berisi logika, Presenter sepenuhnya mengelola status. Supervising Controller: View sendiri melakukan pengikatan data sederhana (misalnya melalui data binding), Presenter hanya campur tangan dalam skenario kompleks. Dalam pengembangan mobile, Passive View lebih sering digunakan — memberikan testabilitas maksimal dan prediktabilitas status layar.
Perbedaan utama antara MVP dan MVC — cara komunikasi dengan View. Di MVC, Controller memiliki referensi langsung ke View (UIViewController.IBOutlets, Activity.findViewById). Di MVP, Presenter berinteraksi dengan View melalui antarmuka ViewContract. Perbedaan ini secara radikal mengubah testabilitas: Objek Mock yang mengimplementasikan ViewContract memungkinkan pemeriksaan logika Presenter tanpa menjalankan aplikasi, emulator, dan framework UI.
| Kriteria | MVC | MVP |
|---|---|---|
| Hubungan dengan View | Langsung (Controller → View) | Melalui antarmuka (Presenter → ViewContract) |
| Pengujian logika | Memerlukan UIKit/Android Framework | Unit test tanpa ketergantungan platform |
| Siklus hidup | Controller hidup dengan layar | Presenter bisa hidup lebih lama (retain) |
| Kompleksitas | Minimal | +1 antarmuka per layar |
| Massive Controller | Masalah umum | Logika di Presenter, View tipis |
Contoh unit test Presenter di Kotlin: mock UserView dibuat, diteruskan ke Presenter, loadUser dipanggil, diperiksa apakah showUser dipanggil dengan data yang benar. Tes dijalankan dalam milidetik, tidak memerlukan emulator. Di iOS serupa — OCMock atau stub protokol memeriksa panggilan metode UserViewProtocol. Di proyek IT Sectr dengan MVP, cakupan logika bisnis dengan unit test mencapai 85–90%, 2–3 kali lebih tinggi daripada proyek MVC serupa.
Kapan MVP lebih diutamakan daripada MVC — di proyek dengan persyaratan stabilitas ketat: aplikasi perbankan, sistem medis, terminal pembayaran. Di bidang ini, biaya kesalahan tinggi dan unit test sangat penting. Di proyek Post-MVP (ketika produk sudah di pasar tetapi basis kode legacy), MVP memungkinkan pemindahan logika secara bertahap dari Massive View Controller ke lapisan yang dapat diuji tanpa menulis ulang seluruh arsitektur.
Kekurangan utama MVP — peningkatan jumlah antarmuka dan manajemen langganan manual. Setiap layar membutuhkan setidaknya satu ViewContract + Presenter, untuk 50 layar — 50 antarmuka dan 50 kelas Presenter. Di MVVM, ViewModel menggantikan Presenter dan menggunakan mekanisme reaktif (LiveData, StateFlow, ObservableObject), yang menghilangkan kebutuhan akan attach/detach manual dan antarmuka ViewContract.
RxJava dan MVP — kombinasi populer di Android 2015–2019. Presenter berlangganan ke Observable dari Repository, menampilkan hasil melalui ViewContract. Masalah: disposable harus dibatalkan secara eksplisit di detachView(), jika tidak kebocoran langganan akan menyebabkan crash saat memperbarui View yang terlepas. Pustaka RxLifecycle dan AutoDispose sebagian mengotomatiskan pembatalan, tetapi menambah ketergantungan. Di IT Sectr, kami beralih dari MVP+RxJava ke MVVM+Flow pada tahun 2020 — kode menjadi lebih pendek 25–30% karena hilangnya ViewContract.
Migrasi dari MVP ke MVVM — proses bertahap. 1) Ganti ViewContract dengan LiveData/StateFlow di Presenter. 2) Hapus metode attach/detach — langganan dilakukan melalui observe(). 3) Ubah nama Presenter menjadi ViewModel. 4) Integrasikan DI (Hilt/Koin) untuk ViewModelFactory. Migrasi satu layar memakan waktu 2–4 jam, seluruh basis kode — 2–4 minggu untuk proyek dengan 50–100 layar. Setelah migrasi, antarmuka ViewContract dihapus, kode menjadi lebih pendek, tes tetap ada.
MVP dalam pengembangan modern — pola ini masih hidup, tetapi kalah dari MVVM dan MVI. Google secara resmi merekomendasikan MVVM dengan Jetpack untuk proyek baru. Apple — MVVM dengan SwiftUI. Namun, pengetahuan tentang MVP wajib untuk bekerja dengan kode legacy: ratusan aplikasi Android di Google Play masih berjalan di MVP, termasuk aplikasi bank besar, peritel, dan perusahaan transportasi. Pemahaman MVP adalah fondasi untuk menguasai MVI dan Clean Architecture, karena Presenter adalah pendahulu langsung dari Use Case dalam terminologi Robert Martin.
Pertanyaan yang Sering Diajukan
Di MVP, Presenter berinteraksi dengan View melalui antarmuka ViewContract, bukan secara langsung. Di MVC, Controller memiliki referensi langsung ke View melalui IBOutlet/findViewById. MVP memungkinkan pengujian Presenter dengan unit test tanpa iOS Simulator atau Android Emulator, karena Presenter tidak bergantung pada UIKit atau Android Framework. MVC memerlukan menjalankan aplikasi untuk menguji controller.
MVP dibenarkan di proyek legacy yang sudah dibangun di atas pola ini dan di aplikasi tanpa dukungan mekanisme reaktif (LiveData, StateFlow, Combine). Untuk proyek baru, Google merekomendasikan MVVM dengan Jetpack (Android) dan Apple merekomendasikan MVVM dengan SwiftUI (iOS). MVP tetap menjadi pilihan terbaik untuk proyek di UIKit murni tanpa Combine ketika pengujian unit logika bisnis diperlukan.
Di Android — gunakan retain-fragment (setRetainInstance(true)) atau ViewModel dari Jetpack. Retain-fragment menyimpan Presenter saat rotasi dan meneruskannya ke Activity baru. ViewModel dari Google — alternatif modern yang secara otomatis mempertahankan status saat rotasi tanpa retain-fragment. Di iOS — Presenter dibuat ulang setiap viewDidLoad, tetapi di-cache di layanan koordinator terpisah.
Minimal 4: antarmuka ViewContract, implementasi ViewContract (Activity/Fragment), Presenter, Model (Repository). Jika menggunakan Dagger/Hilt, modul DI ditambahkan. Untuk 50 layar, itu 200+ kelas. MVVM mengurangi jumlahnya 1 file per layar (ViewContract tidak diperlukan), MVI menambahkan kelas State dan Intent. Jumlah kelas — argumen utama menentang MVP di proyek besar.
Passive View — View tidak berisi logika, Presenter sepenuhnya mengelola status dan data. Supervising Controller — View sendiri melakukan pengikatan sederhana (data binding), Presenter campur tangan dalam skenario kompleks. Dalam pengembangan mobile, Passive View mendominasi — memberikan testabilitas maksimal dan prediktabilitas. Supervising Controller digunakan di framework web (ASP.NET Web Forms, GWT).
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga