Sobes.tech
Middle

What is the difference between MVM and MVP?

sobes.tech AI

Answer from AI

MVM (Model-View-ViewModel) and MVP (Model-View-Presenter) are user interface design patterns.

Differences:

  • Binding:
    • MVM uses two-way data binding between View and ViewModel. Changes in ViewModel automatically reflect in View, and vice versa.
    • MVP typically uses one-way binding: the Presenter updates the View, and the View notifies the Presenter of events.
  • Responsibility of View:
    • In MVM, the View is "passive"; its main task is to display data from the ViewModel and send user actions back to the ViewModel. The presentation logic is entirely in the ViewModel.
    • In MVP, the View is also "passive," but the Presenter fully manages its updates and reactions to events. The View informs the Presenter of events, and the Presenter decides how to react and update the View.
  • Testability:
    • The ViewModel in MVM is easy to test as it does not depend on UIKit/AppKit.
    • The Presenter in MVP is also well testable, but its dependency on an abstract View interface may require mocking.
  • Dependencies:
    • MVM: View depends on ViewModel, ViewModel depends on Model.
    • MVP: View depends on Presenter, Presenter depends on Model. View and Presenter are connected via interfaces.
  • Coordination:
    • In MVM, the View calls commands or methods in the ViewModel in response to user actions.
    • In MVP, the View calls methods in the Presenter through an interface, and the Presenter calls methods to update the View also via an interface.

Example of MVM (schematic):

// Model
struct User {
    let name: String
}

// ViewModel
class UserViewModel { // ObservableObject in SwiftUI or KVO/Reactive in UIKit
    @Published var userName: String = "" // Or Observable in Reactive frameworks

    private var user: User?

    func loadUser() {
        // Load data from Model
        user = User(name: "John Doe")
        userName = user?.name ?? ""
    }

    func changeName(newName: String) {
        // Handle logic and update Model (if necessary)
        // user?.name = newName // If Model is mutable
        userName = newName
    }
}

// View (SwiftUI)
struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        VStack {
            Text(viewModel.userName) // Binding
            Button("Load User") {
                viewModel.loadUser() // Action binding
            }
            TextField("New Name", text: $viewModel.userName) // Two-way binding
        }
        .onAppear {
            viewModel.loadUser() // Example call on appear
        }
    }
}

Example of MVP (schematic):

// Model
struct User {
    let name: String
}

// View Protocol
protocol UserViewProtocol: AnyObject {
    func display(userName: String)
    func showLoadingIndicator()
    func hideLoadingIndicator()
}

// Presenter
class UserPresenter {
    private weak var view: UserViewProtocol?
    private var user: User?

    init(view: UserViewProtocol) {
        self.view = view
    }

    func viewDidLoad() {
        view?.showLoadingIndicator()
        // Asynchronous data load from Model
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.user = User(name: "Jane Doe")
            self.view?.hideLoadingIndicator()
            self.view?.display(userName: self.user?.name ?? "")
        }
    }

    func didTapChangeNameButton(newName: String) {
        // Handle logic and update Model (if necessary)
        // user?.name = newName // If Model is mutable
        view?.display(userName: newName)
    }
}

// View (UIKit)
class UserViewController: UIViewController, UserViewProtocol {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
    @IBOutlet weak var nameTextField: UITextField!
    private var presenter: UserPresenter!

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = UserPresenter(view: self)
        presenter.viewDidLoad()
    }

    // MARK: - UserViewProtocol
    func display(userName: String) {
        nameLabel.text = userName
    }

    func showLoadingIndicator() {
        loadingIndicator.startAnimating()
    }

    func hideLoadingIndicator() {
        loadingIndicator.stopAnimating()
    }

    // Trigger presenter actions from UI events
    @IBAction func changeNameButtonTapped(_ sender: UIButton) {
        if let newName = nameTextField.text {
            presenter.didTapChangeNameButton(newName: newName)
        }
    }
}