-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Covert Coordinators #19
Comments
I like this, but what if we move the responsibility of this to viewmodel, I think it should know where it's going, since it's asking for a particular path aka URL if we're on web for example, it's the same approach to me but instead of declaring a function name you're passing a closure, not a big difference, so I'd prefer readability |
Thanks.
It does know where it's going (albeit nominally at best) and what logic-related data is needed for such navigation. But should it do more? like fully instantiate the destination view controller and dictating how it should be presented? You may try and see how it fares, but I don't go with that. And this is a good opportunity to state some personal architecture principles and explain how I like to think about view models (and presenters in MVP; or whatever qualifies as Logic Controllers). Principles
1. Don't fight the APINo matter what, we eventually have to show some view controllers. So, be it a view model, a dedicated coordinator, or a router, all will have to have a reference of an already shown view controller, and the ability to instantiate another view controller and know the UIKit-specific details about its presentation. Fighting this, in addition to being more work, is hard to enforce without hard code reviews, which leads us to the second principle: 2. Maintain a low surprise factorWhile not being an absolute merit of code, accessibility to less experienced developers is costly for me to lose/risk. Even for experienced developers, MVC is still king. Interaction between view controllers is expected, putting ad-hoc layers between them is surprising. Such interaction may involve a view controller being a delegate to another view controller, or a view controller showing another view controller. This leads us to my last point: How I like to think about logic controllersI like to think about logic controllers as an implementation detail of a view controller. That is, once a view controller receives an event, it reports that as-is to the logic controller. In this system too, the view controller is ready to do whatever the logic controller tells it to do with minimal info as possible. I like logic controllers to only know about their view controllers and no other view controller (or logic controller) even if they're aware of their existence (like we said above: nominally at best). We can use a brain analogy to describe that system. My brain knows about other people; but can only tell my body to shake hands with other people. My brain also doesn't interact directly with other people's brains, it can only tell my body to speak to them. Logic controllers are the brains, and view controllers are the bodies. So, with all this in mind, you can see the reason behind leaving navigation to view controllers, while making use of closures to reach a middle ground between Apple's MVC's unintended chaos, and alien coordinators. Nothing stops anybody from viewing navigation as a part of a view controller's logic, and then implement it there. Maybe the view model of a container view controller can be a suitable place to do this, albeit it'll involve having So, you can try and see. Happy to hear about your results! |
A coordinator is an architectural component that rose into fame the last couple of years or so. It primarily solves the problem of tight coupling between view controllers regarding their presentation and dismissal. You can learn more about it here.
To summarize, coordinators solve the tight coupling problem by extracting destination view controller creation and navigation implementation details from a source view controller to a coordinator object that manages that flow. So, let's have a simple example.
We have an app that is composed of a view controller (let's call it
MainViewController
) that is embedded in aUINavigationController
, and has a button that pushes aSettingsViewController
when tapped.Coordinators avoid doing this:
And do this instead:
Without writing it explicitly, the code for creating and pushing
SettingsViewController
went to the coordinator'sshowSettings()
.Covert Patterns
The goal of this article is doing the above exactly without explicitly spelling out "Coordinator". But let me first show what value I find in doing that.
I like to think of a pattern as a form that code evolves into while seeking a set of goals and respecting a set of principles. I find elegance in code developing healthy patterns without expanding the code's vocabulary as possible. Can we achieve the same goals without the "Coordinator" word (or any equivalent)? Let's see.
Let's start with the above code snippet:
Let's replace the coordinator dependency with a closure:
Good. Now, we need to know how the closure is passed and where it's implemented. Coordinators often rely on a container view controller, commonly a
UINavigationController
. Why not directly use aUINavigationController
subclass then? Let's try that.Here we subclassed our root navigation controller, and supplied
MainViewController
'sshowSettings()
implementation. I find this simpler while maintaining the same gains.Actually I think there's an advantage to this approach over coordinators. If you notice, while using coordinators,
MainViewController
could call any method it wants from thecoordinator
property, even if it's irrelevant. Supplying a single closure to call looks much cleaner to me.Have a look at a sample project where this is implemented. The sample also shows the case of a child flow that would be implemented by so called "child coorindators".
Final Word
As you see, this was an opinionated article. We don't have to agree on this. Feel free to leave your feedback. Thanks for reading.
The text was updated successfully, but these errors were encountered: