Questions about VIPER - Clean Architecture
Asked Answered
P

3

40

I've been reading about Clean Architecture from Robert Martin and more specifically about VIPER.

Then I ran into this article/post Brigade’s Experience Using an MVC Alternative which describes pretty much what I'm currently doing.

After actually trying to implement VIPER on a new iOS project, I've ran into some questions:

  • Is it ok for the presenter to query information in the view or should the "information passing" always start from the view? For example, if the view triggered some action in the presenter, but then, depending on the parameters passed through that action, the presenter might need more information. What I mean is: the user tapped “doneWithState:”, if state == “something”, get information from the view to create an entity, if state == “something else”, animate something in the view. How should I handle this kind of scenario?
  • Lets say a "module" (group of VIPER components) decide to present another module modally. Who should be responsible for deciding if the second module will be presented modally, the first module's wireframe or the second module's wireframe?
  • Also, lets say the second module's view is pushed into a navigation controller, how should the "back" action be handled? Should I manually set a "back" button with an action in the second module's view controller, that calls the presenter, that calls the second module's wireframe that dismiss and tells the first module's wireframe that it was dismissed so that the first module's view controller might want to display something?
  • Should the different modules talk only through the wireframe or also via delegates between presenters? For example if the app navigated to a different module, but after that the user pressed "cancel" or "save" and that choice needs to go back and change something in the first module (maybe display an animation that it was saved or remove something).
  • Lets say a pin was selected on a map, than the PinEditViewController is displayed. When going back, the selected pin's color might need to change depending on use actions on the PinEditViewController. Who should keep the state of the current selected pin, the MapViewController, the MapPresenter or the MapWireframe in order for me to know, when going back, which pin should change color?
Perplexed answered 11/3, 2015 at 19:28 Comment(3)
Hey, have you had any issues using a UITabBar with VIPER architecture?Alliterative
To be honest, I've had a lot of problems with VIPER when it comes to connect "modules" or "components" or however you wanna call them. Right now I'm choosing a custom way every time, basically, whatever I think makes most sense for the circumstance.Perplexed
OK. I will put answer below to as a reference to future strugglers. After asking you the question above i found a sweet swift implementation of VIPER that gets rid of most troubles and verbosity.Alliterative
E
19

1. May the Presenter query information from the view

To answer this to your satisfaction, we need more details about the particular case. Why can't the view provide more context information directly upon callback?

I suggest you pass the Presenter a Command object so the Presenter doesn't have to know what to do in which case. The Presenter can execute the object's method, passing in some information on its own if needed, without knowing anything about the view's state (and thus introducing high coupling to it).

  • View is in a state you call x (opposed to y and z). It knows about its state anyway.
  • User finishes the action. View informs its delegate (Presenter) about being finished. Because it is so involved, it constructs a Data Transfer Object to hold all usual information. One of this DTO's attributes is a id<FollowUpCommand> followUpCommand. View creates a XFollowUpCommand (opposed to YFollowUpCommand and ZFollowUpCommand) and sets its parameters accordingly, then putting it into the DTO.
  • Presenter receives the method call. It does something with the data no matter what concrete FollowUpCommand is there. Then it executes the protocol's only method, followUpCommand.followUp. The concrete implementation will know what to do.

If you have to do a switch-case/if-else on some property, most of the time it'd help to model the options as objects inheriting from a common protocol and pass the objects instead of the state.

2. Modal Module

Should the presenting module or the presented module decide if it's modal? -- The presented module (the second one) should decide as long as it's designed to be used modally only. Put knowledge about a thing in the thing itself. If its presentation mode depends on the context, well, then the module itself can't decide.

The second module's wireframe will receive message like this:

[secondWireframe presentYourStuffIn:self.viewController]

The parameter is the object for which presentation should take place. You may pass along a asModal parameter, too, if the module is designed to be used in both ways. If there's only one way to do it, put this information into the affected module (the one presented) itself.

It will then do something like:

- (void)presentYourStuffIn:(UIViewController)viewController {
    // set up module2ViewController

    [self.presenter configureUserInterfaceForPresentation:module2ViewController];

    // Assuming the modal transition is set up in your Storyboard
    [viewController presentViewController:module2ViewController animated:YES completion:nil];

    self.presentingViewController = viewController;
}

If you use Storyboard Segues, you'll have to do things a bit differently.

3. Navigation hierarchy

Also, lets say the second module's view is pushed into a navigation controller, how should the "back" action be handled?

If you go "all VIPER", yes, you have to get from the view to its wireframe and route to another wireframe.

To pass data back from the presented module ("Second") to the presenting module ("First"), add SecondDelegate and implement it in FirstPresenter. Before the presented module pops, it sends a message to SecondDelegate to notify about the outcome.

"Don't fight the framework", though. Maybe you can leverage some of the navigation controller niceties by sacrificing VIPER pure-ness. Segues are a step into the direction of a routing mechanism already. Look at VTDAddWireframe for UIViewControllerTransitioningDelegate methods in a wireframe which introduce custom animations. Maybe this is of help:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[VTDAddDismissalTransition alloc] init];
}


- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source
{
    return [[VTDAddPresentationTransition alloc] init];
}

I first thought that you'd need to keep a stack of wireframes similar to the navigation stack, and that all "active" module's wireframes are linked to one another. But this isn't the case. The wireframes manage the module's contents, but the navigation stack is the only stack in place representing which view controller is visible.

4. Message flows

Should the different modules talk only through the wireframe or also via delegates between presenters?

If you directly send another module B's object a message from Presenter A, what should happen then?

Since the receiver's view is not visible, an animation cannot start, for example. The Presenter still has to wait for the Wireframe/Router. So it has to enqueue the animation until it becomes active again. This makes the Presenter more stateful, which makes it harder to work with.

Architecture-wise, think about the role the modules play. In Ports/Adapters architecture, from which Clean Architecture burrows some concepts, the problem is more evident. As an analogy: a computer has many ports. The USB port cannot communicate with the LAN port. Every flow of information has to be routed through the core.

What's at the core of your app?

Do you have a Domain Model? Do you have a set of services which are queried from various modules? VIPER modules center around the view. The stuff modules share, like data access mechanisms, don't belong to a particular module. That's what you may call the core. There, you should perform data changes. If another module becomes visible, it pulls in the changed data.

For mere animation purposes, though, let the router know what to do and issue a command to the Presenter depending on the module change.

In VIPER Todo sample code:

  • The "List" is the root view.
  • An "Add" view is presented on top of the list view.
  • ListPresenter implements AddModuleDelegate. If the "Add" module is finished, ListPresenter will know, not its wireframe because the view is already in the navigation stack.

5. Keeping state

Who should keep the state of the current selected pin, the MapViewController, the MapPresenter or the MapWireframe in order for me to know, when going back, which pin should change color?

None. Avoid statefulness in your view module services to reduce cost of maintaining your code. Instead, try to figure out whether you could pass a representation of the pin changes around during changes.

Try to reach for the Entities to obtain state (through Presenter and Interactor and whatnot).

This doesn't mean that you create a Pin object in your view layer, pass it from view controller to view controller, change its properties, and then send it back to reflect changes. Would a NSDictionary with serialized changes do? You can put the new color in there and send it from the PinEditViewController back to its Presenter which issues a change in the MapViewController.

Now I cheated: MapViewController needs to have state. It needs to know all pins. Then I suggested you pass a change dictionary around so MapViewController knows what to do.

But how do you identify the affected pin?

Every pin might have its own ID. Maybe this ID is just its location on the map. Maybe it's its index in a pin array. You need some kind of identifier in any case. Or you create an identifiable wrapper object which holds on to a pin itself for the duration of the operation. (That sounds too ridiculous for the purpose of changing the color, though.)

Sending Events to Change State

VIPER is very Service-based. There are lots of mostly stateless objects tied together to pass messages along and transform data. In the post by Brigade Engineering, a data-centric approach is shown, too.

Entities are in a rather thin layer. On the opposite of the spectrum I have in mind lies a Domain Model. This pattern isn't necessary for every app. Modeling the core of your app in a similar fashion may be beneficial to answer some of your questions, though.

As opposed to Entities as data containers into which everyone might reach through "data managers", a Domain protects its Entities. A Domain will inform about changes proactively, too. (Through NSNotificationCenter, for starters. Less so through command-like direct message calls.)

Now this might be suitable for your Pin case, too:

  • PinEditViewController changes the pin color. This is a change in a UI component.
  • The UI component change corresponds to a change in your underlying model. You perform the changes through the VIPER module stack. (Do you persist the colors? If not, the Pin Entity is always short-lived, but it's still an Entity because its identity matters, not just its values.)
  • The corresponding Pin has changed color and publishes a notification through NSNotificationCenter.
  • By happenstance (that is, Pin doesn't know), some Interactor subscribes to these notifications and changes its view's appearance.

Although this might work for your case, too, I think tying the edit

Everlasting answered 13/3, 2015 at 11:0 Comment(12)
1. The view could provide the information in the method call, but that information might not be used depending on the state. The only problem I find with using the Command here is that the logic for doing something in that callback would stay in the view (which goes against VIPER) and this decision needs knowledge from the presenter. Here is the specific case for that #28841457Perplexed
2. So basically the second module should know how to present itself right?Perplexed
3. The VTDAddWireframe does what I described, except that it doesn't notify the first wireframe about it, which is exactly my problem, the first view controller needs to do something after the second view controller is popped from the navigation controller, depending on what the user did inside the second view controller before going back. Should this be done like the VIPER TODO example, with delegates connecting presenters or should it be done through the wireframes? Or through interactors?Perplexed
I think my other question explains more specifically my scenario #28893333Perplexed
5. About state, I also have to keep a placeDetails variable either in PinEditViewController or PinEditPresenter, because if the user is editing, but doesn't press "save", the placeDetails should not change, so when he presses "cancel", that placeDetails should be used to re-update the PinEditView with the old values. Who should keep this state?Perplexed
I updated the answer for (2): yes, the presented module should decide in most cases.Everlasting
About (5), I don't know what you'd need the placeDetails for if nothing changes. When the user cancels, it's basically a "no operation". So no data passing is needed either. (Maybe here isn't the best way to address 5 separate questions in a discussion-like manner.)Everlasting
Ad (3): without knowing specifics, I would try to make Module1Presenter implement Module2Delegate, which essentially addresses your problem. Before the 2nd module pops, it calls its delegate to notify it about the outcome. Since the 1st module is still "active" but not visible, this should work.Everlasting
Could we start a chat or something more direct if you have the time to help me? I have a lot of questions besides those 5 =). Most of them related to how data is passed from one module to another.Perplexed
(3) The problem is that there would be a lot of connections between modules. The modules would be connected via their Wireframes and vie their Presenters. Also I need to notify the first module before and after the animation of going back, so if I do this with a presenter delegate, the wireframe would also have to notify back its presenter.Perplexed
(5) If the user saved the pin, start editing again, change the name, decide he doesn't want to save the changes and press cancel, then I should update the views back to their old values, based on the placeDetails. I also need placeDetails at least to hold the coordinates before the place is created, since those are not presented in the view. This can either be stored in the presenter, viewController or interactor. Also, I should hold the identifier for the place being edited (GUID), but where?Perplexed
Sure, you find contact details on my website! @5: the view should not have changed until the form is committed in the first place. Don't live-edit on view data. Create a change model and apply the changes.Everlasting
A
9

This answer may be a bit unrelated, but I'm putting it here for reference. The site Clean Swift is an excellent implementation of Uncle Bob's "Clean Architecture" in swift. The owner calls it VIP (it still contains the "Entities" and the Router/wireframe though).

The site gives you XCode templates. So let's say you want to create a new scene (he calls a VIPER modules, "scenes"), All you do is File->new->sceneTemplate.

This template creates a batch of 7 files containing all the headache of the boilerplate code for your project. It also configures them so that they work out of the box. The site gives a pretty thorough explanation of how every thing fits together.

With all the boiler plate code out of the way, finding solutions the questions you asked above is a bit easier. Also, the templates allow for consistency across the board.

EDIT -> In regards to the comments below, here's an explanation as to why I support this approach -> http://stringerstheory.net/the-clean-er-architecture-for-ios-apps/

Also this one -> The Good, the bad, and the Ugly about VIPER in iOS

Alliterative answered 20/12, 2015 at 22:0 Comment(8)
"The site Clean Swift is an excellent implementation of Uncle Bob's VIPER architecture in swift" - I would disagree. The VIP Cycle is not what Uncle Bob ever said. A view should not directly access the business logic layer - so instead of having the cycle VC->I->P->VC you should have VC->P->I and have weak back references via delegation. A View controller should only interact with the presentation layer...Abbie
I think you are looking at VC-I-P and comparing it with VC-P-I on a visual level and ending up with a wrong conclusion. on this site We see that The Presenter knows when to present the user interface. It gathers input from user interactions so it can update the UI and send requests to an Interactor. In the iOS world, this is the viewController. So the View->Presenter in VIPER becomes View->Controller in iOS. Therefore we get V->C->I->P in the VIP model. Understand bob's principle fully, then you can port it to any language. agree?Alliterative
From my point of view the view controller IS the view (together with all UIView subclasses). It knows UIKit, handles interface orientations etc. I think it is a definition of what the view controller is.Abbie
I do not understand how you can call a viewController a view. A view controller like the name suggests, is a controller of a bunch of views. And if you look at its function, you will see that it does exactly the job of the Presenter in VIPER model. Take a look at VIP model again. Its pretty much the same Architecture. The only variation is the router. If VIP is so bad, then can you suggest an iOS project online that has a good implementation of the VIPER model (where you can use storyboards etc) ?Alliterative
The only thing I mean is, that you SHOULD see it as a view. Otherwise you would not need the presenter (if this is already the view controller). We want to keep view controllers dumb... and thats only possible if you have a layer BEFORE the view controller which gets results from the interactor and transforms this into a structure the view controller needs. IMO view controllers should only know about layout constraints, animations and are completely passive because they get all information pushed from the presenter.Abbie
VIP's creator spoke on this. VIPER is a model. We should make the Model fit the language, and not force the language to fit the model. In iOS, the VC is so closely intertwined with the view, that making the VC totally dumb is "going against the grain". VC knows when buttons are clicked, just like Presenter. Knows when events happen, just like presenter etc etc etc. He made a decision to follow Bob's principle without tearing apart the structure of iOS. Making VC totally dumb just to religiously following the Model brings no extra benefit. So he made the call to flow it like VIP. But i get you.Alliterative
I also get your point. But I think its not religious to make view controllers dumb. VCs are hard to test, so I like it to move as much code as possible to the real presenter. And you should make your codebase testable. Another example would be: I have connected an IBAction to my VC - but the logic of the event (maybe start something in the interactor) is implemented in the presenter. So I can replace my VC with a TableVC, just implement the presenters output interface and have to touch no logical code at all. And bum: a new UI.Abbie
Look at his examples again. There is no BusinessLogic (BL) in the VC. The only "logic" the VC has is things that relate to the VC. I am currently developing a large scale project using this model. The only logic in my VC is things that control views: Like opacity fading, effects etc. But all BL exists in the interactor. VC testing just include visual stuff. Interactor testing includes BL. The model is essentially the same. This comments is getting long so I guess we can disagree. But i totally get you. Maybe you can show me a strict implementation in swift. But this i found was the best.Alliterative
D
3

Most of your questions are answered on this post: https://www.ckl.io/blog/best-practices-viper-architecture (sample project included). I suggest you pay special attention to the tips for Modules initialization/presentation: it's up to the source Router to do it.

Regarding back buttons, you can use delegates to trigger this message to the desired module. This is how I do it and it works great (even after you insert push notifications).

And yes, modules can definitely talk to each other by using delegates as well. It's a must for more complex projects.

Dalpe answered 11/4, 2017 at 1:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.