MVP — what it is, the Model-View-Presenter pattern in iOS and Android

Author: IT Sectr Published: 2026-02-16 Reading time: 10 min

MVP (Model-View-Presenter) is an architectural pattern where the Presenter acts as an intermediary between the Model and the View through the ViewContract interface. Unlike MVC, where the Controller directly manages the View through UIKit, the Presenter does not depend on the framework — it works through abstraction, making it testable without Android SDK or UIKit. MVP is widely used in Android development before Jetpack and remains relevant for legacy projects. Read more in Martin Fowler's article.

Key Takeaways

  • MVP — three components: Model (data), View (interface), Presenter (logic and state)
  • ViewContract — the interface through which the Presenter communicates with the View, ensuring testability
  • Presenter — contains all business logic, independent of Android/iOS platform classes
  • Passive View — the View is maximally passive, only displaying data on Presenter commands
  • MVP vs MVC — Presenter is tested with unit tests, Controller in MVC depends on UIKit/Android Framework

What is MVP: the essence of the Model-View-Presenter pattern

MVP (Model-View-Presenter) is an architectural pattern proposed by Martin Fowler in the early 2000s as an evolution of MVC to improve testability of the user interface. Model manages data and business logic, View handles rendering and user input processing, Presenter is the central component that receives events from View, retrieves data from Model and forms the state for display.

The main difference between MVP and MVC — the Presenter does not have a direct reference to the View. Instead, the Presenter interacts with the View through the ViewContract interface. The View implements this interface and passes itself to the Presenter. This breaks the dependency on UIKit (iOS) or Android Framework — the Presenter can be tested in isolation with a mock implementation of ViewContract. In MVC the UIViewController controller directly updates UILabel, in MVP the Presenter calls view.showName(name), and the View decides how to display it.

ComponentResponsibilityTestability
ModelData, business logic, network callsUnit tests (independent of UI)
ViewUI rendering, passing events to PresenterMock implementation via interface
PresenterBusiness logic, state management, navigationUnit tests (via ViewContract mock)

Single Responsibility Principle in MVP is followed more strictly than in MVC: View is only responsible for rendering, Model for data, Presenter for logic and coordination. In real projects the Presenter takes 40–60% of the screen code, View — 20–30%, Model — 20–30%. This distribution allows testing key business logic without launching an Android emulator or iOS simulator.

MVP in Android: Presenter, ViewContract and Activity

MVP in Android uses Activity or Fragment as the View, which implements ViewContract — an interface with data display methods. The Presenter is created in the Activity, attaches the View to itself and manages data loading. When the screen rotates, the Activity is recreated — the Presenter can be preserved through a retain fragment or external storage, solving the state loss problem characteristic of pure MVC.

kotlin
// ViewContract — interface for Presenter to communicate with View
interface UserView {
    fun showLoading()
    fun hideLoading()
    fun showUser(user: User)
    fun showError(message: String)
}

// Presenter — testable logic layer
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 ?: "Unknown error")
            }
        }
    }
}

// View (Activity) implements the 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) { /* update UI */ }
    override fun showLoading() { /* show ProgressBar */ }
    override fun hideLoading() { /* hide ProgressBar */ }
    override fun showError(message: String) { /* show Snackbar */ }
}

Lifecycle management is a key problem of MVP on Android. The Activity is destroyed on screen rotation, and presenter.attachView() is called again in onCreate(). If data loading is asynchronous (RxJava, coroutines), by the time it completes the View may be detached. The solution is to cancel subscriptions in detachView() or use the Loader from Support Library (for projects without Jetpack). At IT Sectr we used the MVP + RxJava combination for years in commercial projects — the pattern is stable but requires discipline in subscription management.

Retain fragments — a mechanism for preserving the Presenter on screen rotation. A fragment without UI (setRetainInstance(true)) outlives the Activity and holds a reference to the Presenter. When the Activity is recreated, the fragment passes the same Presenter to the new Activity. Retain fragments are deprecated since AndroidX, but their pre-Jetpack analog (Fragment.setRetainInstance) still works in legacy projects. In modern development Google recommends ViewModel instead of retain fragments.

MVP in iOS: Presenter and View Protocol

MVP in iOS is built through a View protocol. UIViewController implements the protocol, the Presenter does not import UIKit and is purely testable. Unlike Apple MVC, where UIViewController itself contains logic and direct IBOutlet connections, the Presenter manages state and commands the View through protocol methods. The View does not make decisions — it executes Presenter commands: showUser, showLoading, navigateToProfile.

swift
import Foundation

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

// Presenter — pure logic, no 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) implements the 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
    }
    // ... rest of protocol methods
}

Weak reference to the View is mandatory in iOS MVP. A UIViewController can be destroyed (pop from navigation stack), and its closure in the Presenter would create a retain cycle. A weak reference (weak var) ensures the View is released when it leaves the screen, regardless of asynchronous operations in the Presenter. In Android a similar problem is solved through detachView() — calling it in onDestroy() nullifies the reference to the View.

Passive View vs Supervising Controller — two MVP variants by Martin Fowler. Passive View: the View contains no logic, the Presenter fully manages the state. Supervising Controller: the View itself does simple data binding (for example, through data binding), the Presenter only intervenes in complex scenarios. In mobile development Passive View is used more often — it provides maximum testability and predictability of the screen state.

Differences between MVP and MVC and testing advantages

The main difference between MVP and MVC is the way of communicating with the View. In MVC the Controller has a direct reference to the View (UIViewController.IBOutlets, Activity.findViewById). In MVP the Presenter interacts with the View through the ViewContract interface. This difference fundamentally changes testability: a mock object implementing ViewContract allows testing the Presenter's logic without launching the app, emulator or UI framework.

CriterionMVCMVP
View connectionDirect (Controller → View)Through interface (Presenter → ViewContract)
Logic testingRequires UIKit/Android FrameworkUnit tests without platform dependencies
LifecycleController lives with the screenPresenter can outlive (retain)
ComplexityMinimal+1 interface per screen
Massive ControllerTypical problemLogic in Presenter, View is thin

Example of a Presenter unit test in Kotlin: a mock UserView is created, passed to the Presenter, loadUser is called, it checks that showUser was called with correct data. The test executes in milliseconds, no emulator required. On iOS similarly — OCMock or a protocol stub verifies UserViewProtocol method calls. In IT Sectr projects with MVP, business logic unit test coverage reached 85–90%, which is 2–3 times higher than in similar MVC projects.

When MVP is preferable to MVC — in projects with strict stability requirements: banking apps, medical systems, payment terminals. In these domains the cost of an error is high, and unit tests are critical. In post-MVP projects (when the product is already on the market but the codebase is legacy), MVP allows gradually extracting logic from Massive View Controller into a testable layer without a complete architectural rewrite.

MVP limitations and transition to MVVM

The main drawbacks of MVP are the growth in the number of interfaces and manual subscription management. Each screen requires at least one ViewContract + Presenter, for 50 screens — 50 interfaces and 50 Presenter classes. In MVVM, ViewModel replaces Presenter and uses reactive mechanisms (LiveData, StateFlow, ObservableObject), eliminating the need for manual attach/detach and ViewContract interfaces.

RxJava and MVP — a popular combination in Android 2015–2019. The Presenter subscribes to an Observable from the Repository, displays the result through ViewContract. The problem: disposable must be explicitly cancelled in detachView(), otherwise a subscription leak will cause a crash when updating a detached View. The RxLifecycle and AutoDispose libraries partially automated unsubscription but added dependencies. At IT Sectr we switched from MVP+RxJava to MVVM+Flow in 2020 — the code became 25–30% shorter due to the elimination of ViewContract.

Migration from MVP to MVVM is a gradual process. 1) Replace ViewContract with LiveData/StateFlow in the Presenter. 2) Remove attach/detach methods — subscription goes through observe(). 3) Rename Presenter to ViewModel. 4) Integrate DI (Hilt/Koin) for ViewModelFactory. Migrating one screen takes 2–4 hours, the entire codebase — 2–4 weeks for a project with 50–100 screens. After migration, ViewContract interfaces are removed, code shrinks, tests remain.

MVP in modern development — the pattern is alive but yields to MVVM and MVI. Google officially recommends MVVM with Jetpack for new projects. Apple — MVVM with SwiftUI. However, knowledge of MVP is mandatory for working with legacy code: hundreds of Android apps on Google Play still run on MVP, including apps of major banks, retailers and transport companies. Understanding MVP is the foundation for mastering MVI and Clean Architecture, as the Presenter is the direct predecessor of Use Case in Robert Martin's terms.

Frequently Asked Questions

How is MVP different from MVC?

In MVP, the Presenter interacts with the View through the ViewContract interface, not directly. In MVC, the Controller has a direct reference to the View through IBOutlet/findViewById. MVP allows testing the Presenter with unit tests without iOS Simulator or Android Emulator, since the Presenter does not depend on UIKit or Android Framework. MVC requires launching the app to test the controller.

When should I use MVP instead of MVVM?

MVP is justified in legacy projects already built on this pattern, and in apps without support for reactive mechanisms (LiveData, StateFlow, Combine). For new projects Google recommends MVVM with Jetpack (Android) and Apple recommends MVVM with SwiftUI (iOS). MVP remains the best choice for projects on pure UIKit without Combine when unit testing of business logic is required.

How to solve the problem of losing the Presenter on screen rotation?

On Android — use a retain fragment (setRetainInstance(true)) or ViewModel from Jetpack. The retain fragment stores the Presenter on rotation and passes it to the new Activity. Google's ViewModel is a modern alternative that automatically preserves state on rotation without retain fragments. On iOS — the Presenter is recreated on each viewDidLoad but is cached in a separate coordinator service.

How many classes are needed for one screen in MVP?

At least 4: ViewContract interface, ViewContract implementation (Activity/Fragment), Presenter, Model (Repository). If Dagger/Hilt is used, a DI module is added. For 50 screens this is 200+ classes. MVVM reduces the count by 1 file per screen (ViewContract is not needed), MVI adds State and Intent classes. The number of classes is the main argument against MVP in large projects.

What is the difference between Passive View and Supervising Controller in MVP?

Passive View — the View contains no logic, the Presenter fully manages state and data. Supervising Controller — the View itself performs simple binding (data binding), the Presenter intervenes in complex scenarios. In mobile development Passive View dominates — it provides maximum testability and predictability. Supervising Controller is used in web frameworks (ASP.NET Web Forms, GWT).

Summary

  • MVP (Model-View-Presenter) — an evolution of MVC with a testable Presenter layer through the ViewContract interface
  • ViewContract — an interface abstracting the View from the Presenter, enabling mock testing
  • Passive View — the dominant MVP variant in mobile development with a passive View
  • Presenter — contains business logic, independent of UIKit or Android Framework
  • MVP vs MVC — MVP solves the testing problem but adds 1 interface per screen
  • Android retain fragments — preserving the Presenter on screen rotation before Jetpack ViewModel
  • Migration to MVVM — replacing ViewContract with LiveData/StateFlow reduces code by 25–30%

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also