How to pass ViewController Reference to Router in VIPER design pattern?
Asked Answered
M

1

6

P.S: This is not a opinionated question. Its a legitimate doubt on wiring various modules in VIPER. Its a theoretical question so no code attached. I just need to know how do we wire up View-Presenter-Router in this specific case without breaking ground rules of VIPER

I am trying my hands-on with VIPER for the first time. Here is my fundamental understanding of VIPER.

View: Supposed to show UI Controls and capture IBActions and call its presenter's delegate method to process events

Presenter: Will process all the UI related data and prepares data for rendering and hands over the data back to View. Whenever a screen transition is required it calls its router and asks the router to perform transition

P.S: Presenter will not have any UIComponents in it. So no import UIKit statement in presenter.

Router: is responsible for performing screen transitions usually does it with the help of wireframes (optional but good to have such class in app)

Interactor: Contains all business logic.Presenter will call Interactor whenever processing based on business logic is required.

Entity: POJO classes (Simple Swift Objects or Core data entities).

Now comes the question:

If my assumptions are right, Presenter is supposed to be a plain Swift class with no UIKit access.

If true, assume I press a button on my ViewControllerA and I need to push another ViewControllerB on top of it, obviously ViewControllerA will talk to PresenterA and tells it that button is tapped, now PresenterA should talk to RouterA and tell it to push ViewControllerB.

Because Router can have access to UIKit I can easily create a new instance of ViewControllerB using storyboard instance or from xib, but in order to push that instance I need the ViewControllerA's instance.

But PresenterA can not hold a reference to ViewControllerA or can be passed as argument to PresenterA in function because UIViewController belongs to UIKit and Presenters are not supposed to have UI statements.

Probable Solutions I can think of:

Solution 1:

While creating Router instance, pass corresponding ViewController instance as a part of its init (Dependency injection phase) that way Router will always have reference to ViewController it belongs to

Solution 2:

Have a Router declare its protocol and implement it in ViewController and whenever reference to ViewController needed use delegate of Router. But this contradicts the rules of VIPER that routers are not supposed to talk to View.

Am I thinking straight? Am I right with my assumptions? If yes what is the correct way to deal with this problem, Please suggest

Mononucleosis answered 27/7, 2018 at 8:43 Comment(5)
Since Router is the object responsible for navigation, it's natural it would hold the reference to ViewControllerA, since it presented it in the first place. Similarly, Router would hold the reference to UINagivationController or UITabBarController if your application uses themEtalon
@mag-zbc : I am in complete agreement with your statement, but my confusion is when to/how to pass the viewController reference to router? At the time of init? As a part of dependency injection? Then it creates a loop because to create ViewController I need router and router takes a reference to ViewController :(Mononucleosis
You don't pass it at all - ViewControllerA is supposed to be presented by Router in the first place, so it should be created in the Router and Router should retain reference to it.Etalon
@mag-zbc: Sorry I think I am confused here, lets say I have loginVC and I need to present HomeVC on login, in that case LoginVC -> LoginPresenter -> LoginRouter and login router will create a instance of HomeVC from storyboard and pushes it. In that case, HomeVC needs to create its own HomePresenter now the question is how to break dead lock I talked above. Please read -> as talks to in statement aboveMononucleosis
There should only be a single Router in entire application, that's the whole point of VIPER - a single object responsible for navigation, not each ViewController having its own router. Also, you speak of ViewControllers having their own Presenters each, where I would argue that ViewControllers should be PresentersEtalon
E
5

Any advice or opinion regarding VIPER for iOS applications is debatable, since VIPER does not fit strictly into iOS's UIKit design. But if I may put my two cents into discussion:

First off, I would argue that UIViewController fits perfectly into the role of Presenter in VIPER pattern, therefore ViewControllerA doesn't need to talk to any class in between itself and Router - just communicate with Router directly.

Secondly, there is supposed to be only one Router object - because application has only one view/navigation stack. For that reason it's ideal to implement a Singleton pattern for Router, so ViewControllerA (or Presenter, if you'd rather have your ViewControllers perform the role of View in that pattern) doesn't need to keep a reference to Router.

Thirdly, you shouldn't need to pass the reference to ViewControllerA to your Router - Router should already have the reference to it, because Router was supposed to present it in the first place. Something like that:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
{
    // ...

    window?.rootViewController = Router.shared.rootViewController

    // ...
}

class Router
{
    static let shared = Router()

    let rootViewController = ViewControllerA() // or UINavigationController, or UITabBarController etc.
}

Router should keep track of navigation stack and keep reference to currently presented ViewController

But that is, like, my opinion.

Etalon answered 27/7, 2018 at 9:33 Comment(4)
@mag-zbc : Thanks a lot for taking your time and explaining things :) Hence +1 Gimme a minute to verify the fact you mentioned :)Mononucleosis
@mag-zbc : Having a singleton wireframe/router makes sense :) Thanks lemme explore more on that :) Though I dont agree with making ViewController the presenter (merging operation of View and Presenter in ViewController) because than we will have same module structure as one we had in MVVM :) The whole idea was to avoid fat module issue that we faced with MVVM and making concern separation and making unit test more deeper and cleaner :)Mononucleosis
Like I said, it's debatable. ViewController as Presenter doesn't necessarily have to be fat, since business logic belongs to Interactor. Similarity in certain aspects to other design patterns is not a fault by itself.Etalon
agreed, just don't wanna mess up with the design pattern scared of loosing out on the benefit design pattern inherently providesMononucleosis

© 2022 - 2024 — McMap. All rights reserved.