Lars Richter
Lars Richter's Blog

Follow

Lars Richter's Blog

Follow
My Experience With The VIP Pattern

Photo by Alvaro Reyes on Unsplash

My Experience With The VIP Pattern

Lars Richter's photo
Lars Richter
ยทMar 5, 2023ยท

5 min read

Table of contents

  • But why?
  • What is the VIP pattern?
  • The Data Flows
  • Is that it?

At work, we decided to switch from our current MVVM-C pattern to a VIP(-C) approach. It's not strictly implemented as suggested on https://clean-swift.com, but it is based on it. And of course, it is a gradual migration. It would be crazy to pause all ongoing projects for months just to bring the entire codebase to another architecture. Instead, we will create all new scenes using VIP. Additionally, some devs (like myself) will use their Hackdays to migrate scenes from MVVM to VIP from time to time.

But why?

The way we implement the MVVM-C pattern in our codebase has a few downsides. And these problems got worse with the introduction of SwiftUI. So we evaluated other patterns like VIP and VIPER. In the end, we agreed to give VIP a chance.

What is the VIP pattern?

The thing that appealed to me the most in this pattern is the uni-directional flow. There is no "magic" going on. If an event happened in the view/UI, the View informs the Interactor about that. The Interactor does its job and informs the Presenter about the changes. The Presenter will then transform this information into a ViewModel for the View to render the relevant changes.

Let's look at the individual components.

View

Well... that's kind of obvious. The V in VIP stands for the view. It can be a SwiftUI view or a UIKit ViewController. So it's the component that defines the user interface.

Interactor

This is where "the magic" happens. Well... not really. Let me try again. This is where the logic happens! Yes, that sounds better. The Interactor is the component that calls/triggers the relevant business logic. If a button is pressed, the view finished loading, or any other event happens, the view calls the interactor to do the actual logic. This means the Interactor is the component that communicates with other services, repositories, coordinators, you name it.

Presenter

This is where the magic mapping happens. When the Interactor is done with his business, the user expects some sort of visual feedback. So someone (or maybe better "something") will have to map the current state of things to changes in the view model, which then will be used to update the view. And this someone is the Presenter. It gets all the relevant information from the interactor and maps it into a view model. But, in my implementation, it has one more responsibility. If the view holds the interactor, the interactor holds the presenter, and the presenter holds the view, we have a perfect retain cycle. We don't want that, do we? So the presenter is responsible to hold a weak reference to the view.

The Data Flows

When we create a new scene using VIP, we start with the data flows. We look at the scene and identify events that can happen during the scene. For example CloseButtonTapped, InputTextChanged, or TextMessageReceived. Some of these events will run the entire cycle, but some won't. Let's look at these examples:

InputTextChanged

This data flow will most likely run the entire VIP cycle. The user changes the text in an input field and this will trigger the Interactor. The Interactor might check the text for typos, check for maximum or minimal length, or do any other form of validation. When the interactor is done, it will inform Presenter about the result of its validation. Let's stick with the "minimum length" example. If the input text reached the minimum length, the Interactor will pass minimumLengthReached: true to the Presenter. The Presenter will now create the view model based on this information. If the minimum length is reached, it might pass a view model with sendButtonEnabled: true to the View.

That's it. The full cycle is done.

CloseButtonTapped

This data flow will be different. So what happens if the user taps the close button? The View will inform the Interactor about this event. And the interactor? It will close/finish the scene. No need for further UI updates inside of our current scene. So the only event is the call of the Interactor.

TextMessageReceived

Sometimes UI updates happen due to external events. One example would be a push notification that informs your app about a text message from another user. But this is not an event that originates in the View. It's based on an event in the background. That's where the Interactor comes into play. The Interactor subscribed to the relevant notifications. As soon as the notification is received, it will start the flow and send the important information to the Presenter, which will then map the data into the view model and pass it to the View.

Modeling the Data Flows

In the VIP pattern, a flow from the View to the Interactor is called a Request. A corresponding Response is then passed to the Presenter. And, you might have guessed it, the Presenter sends a ViewModel to the View. So the data flows for our examples can be defined like this:

enum MyAwesomeScene {
    enum InputTextChanged {
        struct Request {
            let inputText: String
        }

        struct Response {
            let minimumLengthReached: Bool
        }

        struct ViewModel {
            let sendButtonEnabled: Bool
        }
    }

    enum TextMessageReceived {
        struct Response {
            let message: String
            let author: String
            let sentDate: Date
        }

        struct ViewModel {
            let messageText: String
            let messageHeader: String
            let formattedSentDate: String
        }
    }

    enum CloseButtonTapped {
        struct Request {}
    }
}

I like that every developer can now look at these data flows and can easily see what will happen during which event/flow. And yes, I think it's a good idea to also define "empty" flows like the CloseButtonTapped.Request even if you don't have to provide any data.

Is that it?

No, there is more. But I think it might be enough to get an idea of how and why we switched to this approach. Of course, there are more things to discuss like navigation, the concrete implementation of the view and its view state, how to test these scenes, and more. Let me know if you are interested in these topics. Maybe I will create more blog posts about it.

Happy coding. ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป

ย 
Share this