VIPER architecture: Our best practices to build an app like a boss
Technical Opinion

VIPER architecture: Our best practices to build an app like a boss

The dev team at Cheesecake Labs has been using VIPER for iOS and Android mobile app development for over one year and we just love this clean architecture!

This article summarizes our best practices on the VIPER architecture, using code examples from our VIPER boilerplate. The code samples used here are in Swift, but all concepts mentioned may be applied to an Android project developed with VIPER, either using Java or Kotlin.

If you still need to get familiar with basic VIPER concepts and how this architecture can make your code more organized and scalable, I suggest you read the following articles:

So, are you ready to learn our best practices on how to build an app with VIPER?
Funny image of a man vibrating and saying let us go for VIPER

Project structure for real VIPER modules decoupling

Image of Xcode Navigation Bar with the folder structure for iOS VIPER project

VIPER’s folder structure for iOS

You can see that we keep all VIPER’s modules as decoupled as possible, saving us from future headaches when the project grows or specification changes. If you delete any of them, you should get several errors just on the Routers that reference that model – but not on Views, Presenters, Interactors, Data Managers or Entity classes.

One key point to help you truly decouple your modules is to keep all entities on a separate folder, linking them to the project itself and not to any specific module.

Also, using Data Managers to perform API requests and manipulate local database is an excellent way of increasing the project organization, but requires some attention:

  1. Keep Data Managers inside the Utils folder, separating them from the modules;
  2. Group methods for similar entities – such as User, Profile and CompanyProfile – into one Data Manager to avoid the overhead of creating one Data Manager for each entity;
  3. Split each Data Manager into Local and API classes to make the code on the Interactors much more readable:

class MainSearchInteractor {
    
    // Properties
    weak var output: MainSearchInteractorOutput?
    var apiDataManager = ProfileApiDataManager()
    var localDataManager = ProfileLocalDataManager()
}

extension MainSearchInteractor: MainSearchUseCase {
  
    // Code below show how interactor get data from API and then saves it on local DB with separate data managers
    func searchProducts(with searchTerm: String, onPage page: Int) {
        
        self.apiDataManager.searchProducts(with: searchTerm, forPage: page) { (products) in
            if let products = products {
                self.localDataManager.updateSearchResultFavorites(products) { (products) in
                    self.output?.onFetchProductsSuccess(Array(products), shouldAppend: page != 1)
                }
            } else {
                self.output?.onFetchProductsSuccess(nil, shouldAppend: page != 1)
            }
        }
    }
}

Generating VIPER’s files faster and furiously

If you’ve already developed using VIPER, you’ve had the bad experience of creating all the 20+ Swift files needed for a simple screen with three tabs on the navigation bar. But there’s a light at the end of the tunnel: this amazing Xcode plugin which automates the generation of all VIPER files for one module with three clicks.

If you think that that’s too much, meet Generamba: a code generator designed to create VIPER modules from the terminal, quite easy to customize for any other classes.

Define contracts with protocols

Just like for us humans, contracts on VIPER are a voluntary agreement between two parties (module components) concerning the rights (methods) and duties (arguments) that arise from agreements. At Cheesecake Labs, we use protocols to define the methods that a module component can call from other components on the same module.

However, before start writing the code for a new View or Presenter, for example, think about the information flow between both components and declare their methods on the Contract first.


// MainSearchContract.swift
import Foundation

protocol MainSearchView: BaseView {
    func showCustomError(_ message: String?)
    func updateVisibility(onSearchController willBeActive: Bool)
    func showSearchResult(_ products: [Product]?, shouldAppend: Bool)
}

protocol MainSearchPresentation: class {
    func onViewDidLoad()
    func onWillPresentSearchController()
    func onSearchTermChanged(to searchTerm: String)
    func onProductFavoriteChanged(_ product: Product, to isFavorite: Bool)
    func onProductSelected(_ product: Product)
    func onInfiniteScrollTriggered()
}

protocol MainSearchUseCase: class {
    func searchProducts(with searchTerm: String, onPage page: Int)
    func updateProductFavorited(_ product: Product, to newValue: Bool)
}

protocol MainSearchInteractorOutput: class {
    func onFetchProductsSuccess(_ products: [Product]?, shouldAppend: Bool)
    func onFetchProductsFailure(message: String)
}

protocol MainSearchWireframe: class {
    func showProductScreen(delegate: ProductScreenDelegate, product: Product?)
}

The Xcode plugin mentioned before will also create a ModuleNameContract.swift file with all protocols, waiting for your declaration of the necessary methods. Once those protocols are defined, you have complete control of the information flow between the components of a VIPER module.

Image of a judge showing a contract paper and asking whose signature is that, making reference to a VIPER contract

Automate modules initialization on the Router

Before presenting the View of a VIPER module, you need to make sure all components have been properly initialized. I can think of at least 3 very different ways of doing it, but the flow below is the best option we’ve came up with. The ace in the hole here is to have a static function on each Router to initialize its own module together with some UIViewController and UIStoryboard extensions. Then, if module A wants to present Module B:

  1. Module A’s Router will call Module B’s static function to initialize all of its components, returning a View.
  2. Module A’s Router presents Module B’s View.

As simple as that. Having the module initialization code on its own Router will eliminate a bunch of code repetition, specially for huge projects.

You need to create these extensions once:


// ReusableView.swift
protocol ReusableView: class {}

extension ReusableView {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

// UIViewController.swift
extension UIViewController: ReusableView { }

// UIStoryboard.swift
extension UIStoryboard {
    func instantiateViewController() -> T where T: ReusableView {
        return instantiateViewController(withIdentifier: T.reuseIdentifier) as! T
    }
}

And then, leave initialization code on the router of each VIPER module:


// MainSearchRouter.swift
class MainSearchRouter {

    // MARK: Properties
    weak var view: UIViewController?

    // MARK: Static methods
    static func setupModule() -> MainSearchViewController {
        let viewController = UIStoryboard(name: MainSearchViewController.storyboardName, bundle: nil).instantiateViewController() as MainSearchViewController
        let presenter = MainSearchPresenter()
        let router = MainSearchRouter()
        let interactor = MainSearchInteractor()

        viewController.presenter =  presenter

        presenter.view = viewController
        presenter.router = router
        presenter.interactor = interactor

        router.view = viewController

        interactor.output = presenter

        return viewController
    }
}

It might seem like a lot of steps, but good news: the aforementioned plugin automates that for us as well! 🙂

However, you’ll need to take some additional steps if you want to fit a UITabBarController or a UIPageViewController into the VIPER architecture. If you need any help, just drop a comment on this post and I’ll prepare a specific Gist for you.

If you’ve came up this far, dear reader, you’re really avid for knowledge. So I’ll give you 3 advices to make sure you’ve fully understood the responsibilities of the Router:

  1. If you need to open a URL when the user clicks a button, call UIApplication.shared.openURL(url) on the Router because you’re navigating (i.e. routing) out of your current module;
  2. The same concept applies for social media sharing: call UIActivityViewController from the Router because iOS will send the user to a View or app out of your current module;
  3. If you’re only calling an Action Sheet to get some user input, that’s an UI component added to your current module. So you can call it from your View, and enjoy straightforward callbacks from the UIAlertController.

Use delegates to send data between VIPER modules

You’ve probably faced a situation where a field on Module A is filled with the selected item of the Module B. So Module A calls Module B when the user clicks the field, and Module B returns the selected item to the existing Module A through the delegate.

Delegates are an awesome approach to send information back and forth between VIPER modules:


// 1. Declare which messages can be sent to the delegate

// ProductScreenDelegate.swift
protocol ProductScreenDelegate {
    //Add arguments if you need to send some information
    func onProductScreenDismissed()
    func onProductSelected(_ product: Product?)
}

// 2. Call the delegate when you need to send him a message

// ProductPresenter.swift
class ProductPresenter {

    // MARK: Properties
    weak var view: ProductView?
    var router: ProductWireframe?
    var interactor: ProductUseCase?
    var delegate: ProductScreenDelegate?
}

extension ProductPresenter: ProductPresentation {

    //View tells Presenter that view disappeared
    func onViewDidDisappear() {

        //Presenter tells its delegate that the screen was dismissed
        delegate?.onProductScreenDismissed()
    }
}

// 3. Implement the delegate protocol to do something when you receive the message

// ScannerPresenter.swift
class ScannerPresenter: ProductScreenDelegate {

    //Presenter receives the message from the sender
    func onProductScreenDismissed() {

        //Presenter tells view what to do once product screen was dismissed
        view?.startScanning()
    }
    ...
}

// 4. Link the delegate from the Product presenter in order to proper initialize it

// File ScannerRouter.swift
class ProductRouter {

    static func setupModule(delegate: ProductScreenDelegate?) -> ProductViewController {
        ...
        let presenter = ScannerPresenter()

        presenter.view = view
        presenter.interactor = interactor
        presenter.router = router
        presenter.delegate = delegate // Add this line to link the delegate
        ...
        }
}

 

And avoid dictionaries to send information between VIPER components

Using a POSO (Plain Old Swift Object) to send information between VIPER’s components is the best approach if you want to be 100% compliant with the VIPER architecture. But sending the Entity itself between VIPER components works fine and removes the overhead of creating POSOs.

Anyway, avoid sending this data using dictionaries if you don’t want to get lost with key names when your project starts growing and changing.

Focus on the VIPER mindset

If you want to take the most of this architecture, it’s important to keep your team completely in sync with the responsibilities of each component of a VIPER module.

Even after understanding the role of each specific component, our team still faced some doubts, mostly influenced by previous experience with MVC.

Homer Simpson is telling other people that he wants to make a few things clear, making reference to VIPER responsabilities.

  1. The View is the one that handles UI elements: it imports UIKit and implements all logic regarding UI elements from its module. TableView logics, for example, are implemented on the View. If you want to make your code more readable, split TableView logics on extensions. If you want to make your project even more concise, use a TableViewDataManager;
  1. The presenter does not import UIKit and does not handle UI elements, but it does prepare the data in the format required by the view and take decisions based on UI events from the view. Do not manipulate any UI element on the presenter, it shouldn’t handle them;
  1. The Interactor can also prepare the data, but for the database. Once the ApiDataManager fetches some data, for example, the Interactor can do some sorting or filtering before asking the LocalDataManager to save the data. But note that the Interactor doesn’t know the view, so it has no idea how the data should be prepared for the view.

Wrapping up

The product team decided to drop out a feature from your project? Or your small project started growing huge? Use proper VIPER architecture and avoid future headaches!

Automating VIPER files creation and modules initialization will eliminate the overhead of working with this – complex at first sight – but clear and awesome architecture. Android developers can also use it as well.

We’ve seen that our approach to VIPER architecture is actually composed of VIPRC modules (View-Interactor-Presenter-Router-Contract), while Entities are decoupled from the modules, along with Data Managers. I know the name VIPRC is not sexy at all, but it allows you to build an app like a boss.

Do you have any other tips for using VIPER architecture on iOS and Android app development? Feel free to share your experience!

About the author

Marcelo Gracietti

Jumped drillships to join great friends on their amazing mission, exploring his developer/entrepreneur skills. Loves traveling and can cook a lasagna better than his grandmother.

Need a team for your projects?
We'd love to hear your ideas!

Connect with us!
  • Ricardo Gehrke Filho

    Great article!
    Indeed VIPER can throw a lot of doubts.

    The module delegate looks a great solution for some problems I had.
    I’ll try to use it.

    Thanks dude!

    • Marcelo Gracietti

      I’m glad you enjoyed it, Ricardo. Once you understand how to define delegates with protocols and how to properly call them, you’ll notice your app development with VIPER will flow seamlessly!

  • Greg Hilston

    Hi Cheesecake Labs. I was wondering if you guys could speak about how you guys use VIPER when dealing with UIPageViewController.

    It seems a little difficult, as the system calls like pageViewController before and after require a UIPageViewController to be returned. This throws a wrench in VIPER, as we don’t rely on the return values, but instead the cyclic nature of the ViewController -> Interactor -> Worker -> Presenter -> ViewController, completing the loop.

    Any explanations of gists are much appreciated! I have not been able to find any other resources on how to best accomplish this.

  • Ron Hernandez

    Hi,

    Nice articles – I’m trying to fit a UITabBarController, I’m still confused as to where this container controller fits in the in the VIPER Architecture. How do the modules for each tab get initialized (assembled ?)

    • Marcelo Gracietti

      Hi Ron,

      To fit a UITabBarController you need to call a CoreTabBarModule, which will initialize all its submodules (tab bars) when its view is loaded.

      I’ll put a sample on the boilerplate to give a more clear idea.

      • Ron Hernandez

        Hi Marcelo,,

        thanks for this – i actually ended up doing something similar.

        again – great article!

  • Ron Hernandez

    any pointers will be greatly appreciated 🙂

    • Marcelo Gracietti

      Answered above 🙂

  • Orlando Schäfer

    Hey,

    glad to see there are more people out there coming to the same conclusion as we did. We also think VIPER is a good architecture approach.

    One question: How do you deal with access control? We began to move all module files into separate frameworks. So we can make use of the public/open keyword. But then it begins to become messy because of all these linking issues when using cocoapods, multiple targets and so on…
    Your boilerplate does not prevent to use the MainSearchViewController in the ProfileApiDataManager…

    Greetings from Karlsruhe

    • Marcelo Gracietti

      Hallo Orlando,

      Nice to see your team is also enjoying VIPER.

      Regarding access control: we keep it simple and don’t do it. Whenever we have more than 1 dev working on a project, we make sure they’re all familiar with the architecture and aware of how modules/classes should be organized and called.

      If for any reason you do want to prevent devs from using classes where they shouldn’t, creating frameworks seems to be a feasible solution but you’ll have to deal with its downsides (which afaik are not VIPER related, but applies to any other architecture).

      Schönen Tag!

      • Orlando Schäfer

        Hi Marcelo,

        thx for your reply!

        Of course, the downsides are not VIPER related.

        This is more a general Swift problem. As soon as you want some restrictions in access control you only have two options: Make a framework or put everything into one file. IMO: Instead of introducing the weird “open” and “fileprivate” keywords in Swift 3 it would have been nice to see other access control possibilities within one target/framework.

        Anyway – thx!
        Cheers, Orlando

  • Balázs Mogyoródi

    Hi! Do you think adding viewmodels and using SwiftBond with this architecture is a good idea?

    • Marcelo Gracietti

      Hi Balázs,

      I think SwiftBond is great for the view-only tasks, such as functional textField bidings and buttons observers. It adds great performance on development, and makes the code much cleaner.

      When it comes to adding ViewModels, (though it would work on VIPER) I wouldn’t use unless your project is really small, with low chances of scaling up soon. When a project becomes complex and huge, it’s hard to control all flow of information if some modules have direct biding between Views and Models.

  • Pau

    Hi Cheesecake Labs, nice post! I was just wondering why you make the view to know about the Entity. Let’s say your entity contains information such as an identifier (you may require it to perform some operations over this object in the API), so the view knows some internal data not related to it. It may also happen you Entity has different (independent) properties, that you you use for different purposes (ie. name, surname, but your view will show somewhere the whole full name but also only your first name somewhere else); in this case, your view needs to generate name + surname to set the label’s text. Wouldn’t it be better to have a view model that the presenter sends to the view (generated by using the Entity)?

    • Marcelo Gracietti

      Great question, Pau.

      The most simple and clean solution that me and most devs adopt at Cheesecake, either for iOS or Android, is to leave all this code on the Entity, avoiding extra ViewModels and extra code.

      The entity from your example you have: identifier (for API), name and surname (from API), and a custom property called fullName (to be used by the View).

      Here an example (https://gist.github.com/gracietti/8fdca4123c29c5e58734af8b29736a04) of an Entity called Events, which is used pretty much everywhere on EventModule, with API specific and View computed vars.

      Thanks to computed vars, code will look awesome on View!

  • I get the following error using ‘boilerplate’, if I create the files from the VIPER Xcode plugin template then I can not instantiate it, I get this ‘Use of unresolved identifier’. I already put the id to the StoryBoard and nothing, but when I create the files by hand if I recognize them but even so in the class Rotuer I get an error when I am called the StoryBoard, which I am doing wrong, if for example I want to move to another Seen from a button as it is done?

  • Bikram Thapa

    Hello Cheesecake Labs,
    I am currently rewriting code in VIPER.

    I got a lot of observers in my view controller .
    With VIPER where should I place my observers ?

    • Marcelo Gracietti

      Hi Bikram,

      Nice to hear you’re using VIPER. You may face some doubts at the beginning, as usual when dealing with a new architecture, but than it pays off.

      Regarding observer, at Cheesecake we usually gather similar observer into one controller and keep it inside Utils > Customs folder. Here an example of a KeyboardController (https://gist.github.com/gracietti/1beb359a4cab56aee9e5af1106a50f4f), with observers to dismiss the keyboard or change view constraints.

  • Bikram Thapa

    Hi, Cheesecake Labs,
    I was currently rewriting code with VIPER.

    I got a lot of observers in my view controller.
    Where should I place the observers in VIPER ?

    • Marcelo Gracietti

      Answered below.