DEV Community

Binoy Vijayan
Binoy Vijayan

Posted on • Updated on

Architectural Pattern - VIP (View-Interactor-Presenter)

VIP (View, Interactor, Presenter) is a clean architecture pattern that emphasises the separation of concerns in your application. It provides a clear and organised structure, making maintaining and testing code easier.

Image description

View takes user inputs and passes them to Interactor in form of requests.

Interactor processes these requests (validates and executes them) and passes the response to the Presenter

Presenter processes these responses (validates and creates ViewModels) and passes them to view to update/render UI.

Here are the advantages and disadvantages of using the VIP architectural pattern:

Advantages:

Separation of Concerns: VIP promotes a clear separation of concerns by dividing the user interface (View) from the business logic (Interactor) and the presentation logic (Presenter). This separation enhances maintainability and testability.

Testability: VIP makes it easier to unit test the application components. Since the business logic resides in the Interactor and the presentation logic in the Presenter, these components can be tested independently of the View, leading to more comprehensive test coverage.

Scalability and Flexibility: VIP allows for greater scalability and flexibility in development. With well-defined interfaces between the View, Interactor, and Presenter, it's easier to make changes to one component without affecting the others, making the application more adaptable to changes over time.

Reduced Dependency: The View is kept passive in VIP, with minimal logic. This reduces the dependency between the presentation logic and the UI framework, making it easier to switch UI frameworks or adapt to different platforms.

Improved Maintainability: By separating concerns and enforcing clear boundaries between components, VIP leads to code that is easier to understand, debug, and maintain over time. This is especially beneficial for larger projects with multiple developers.

Disadvantages:

Complexity: Implementing VIP can introduce additional complexity compared to simpler architectures like MVC. Developers need to carefully manage communication between the View, Interactor, and Presenter, which may lead to more boilerplate code and increased development time.

Learning Curve: VIP may have a steeper learning curve for developers who are not familiar with the pattern. Understanding the responsibilities of each component and how they interact with each other requires some initial effort and may pose a challenge for beginners.

Potential Overhead: In some cases, VIP can introduce additional overhead, especially for smaller projects or simple user interfaces where the benefits of the pattern may not outweigh the added complexity. It's important to weigh the benefits against the costs when deciding whether to adopt VIP for a particular project.

Tighter Coupling Between Components: While VIP aims to keep components loosely coupled, there can still be tight coupling between the View, Interactor, and Presenter, especially when dealing with complex business logic or UI-related tasks. This can make it harder to achieve true separation of concerns in practice.

Increased File Count: VIP often requires creating more files/classes compared to simpler architectures like MVC, which may lead to a larger codebase. This can make navigation and code organisation more challenging, especially for larger projects.

Below is a simple example of how you can implement the VIP (View-Interactor-Presenter) architectural pattern in Swift.

View

import UIKit

// MARK: - View
protocol LoginView: AnyObject {
    func showError(message: String)
    func loginSuccessful(user: User)
}

class LoginViewController: UIViewController, LoginView {
    var presenter: LoginPresenterProtocol!

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

    @IBAction func loginButtonTapped(_ sender: UIButton) {
        presenter.loginButtonTapped(username: "username", password: "password") // Example hardcoded values
    }

    func showError(message: String) {
        print("Error: \(message)")
    }

    func loginSuccessful(user: User) {
        print("Login successful: \(user.username)")
    }
}
Enter fullscreen mode Exit fullscreen mode

Presenter

protocol LoginPresenterProtocol {
    func loginButtonTapped(username: String, password: String)
}

class LoginPresenter: LoginPresenterProtocol {
    private weak var view: LoginView?
    private var interactor: LoginInteractorProtocol

    init(view: LoginView) {
        self.view = view
        self.interactor = LoginInteractor()
    }

    func loginButtonTapped(username: String, password: String) {
        interactor.login(username: username, password: password)
    }
}

Enter fullscreen mode Exit fullscreen mode

Interactor

protocol LoginInteractorProtocol {
    func login(username: String, password: String)
}

class LoginInteractor: LoginInteractorProtocol {
    func login(username: String, password: String) {
        // Simulate business logic, e.g., fetching data from a server or database
        if username == "username" && password == "password" {
            let user = User(username: username, password: password)
            (UIApplication.shared.delegate as? AppDelegate)?.user = user
        } else {
            // Notify presenter of error
            (UIApplication.shared.delegate as? AppDelegate)?.presenter.showError(message: "Invalid credentials")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Entity

// MARK: - Model
struct User {
    let username: String
    let password: String
}
Enter fullscreen mode Exit fullscreen mode

Usage

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var presenter: LoginPresenterProtocol {
        return (window?.rootViewController as! LoginViewController).presenter
    }
    var user: User?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        let viewController = LoginViewController()
        window?.rootViewController = viewController
        window?.makeKeyAndVisible()
        return true
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example:

LoginViewController is the View. It handles user interactions and communicates with the Presenter.

LoginInteractor contains the business logic for login functionality.

LoginPresenter mediates between the View and the Interactor.

The User struct represents the model.

AppDelegate serves as a coordinator, setting up the initial view controller and providing access to the Presenter.

The Presenter is injected into the View during initialisation, establishing the connection between them.

When the login button is tapped, the View calls the loginButtonTapped method on the Presenter, passing the username and password.

The Presenter then calls the appropriate method on the Interactor, which performs the login operation.

Depending on the result of the login attempt, the Interactor notifies the Presenter, which updates the View accordingly.

Summary

Overall, the VIP architectural pattern is well-suited for application development, offering benefits in terms of maintainability, testability, and scalability, especially for larger and more complex applications. However, its implementation requires careful consideration and adherence to best practices to realise its full potential.

MVC MVP MVVM

Top comments (0)