MVVM + Coordinator = 🍰
I've been using the god-mix of the MVVM pattern mixed with the Coordinator pattern to build Tacks - and I'm a huge fan of the approach.
Prior to adopting this, when I was using only MVVM, there was always the question of who was responsible for navigating between the screens - who makes the overarching decisions regarding what to do next? The implementation of which would float awkwardly somewhere between the ViewController (VC) having the presentation of the next screen hard-coded (which hurt reusability), or the ViewModel (VM) somehow nudging the VC and whispering "now go to the success screen" or whatever into it's ear. This really polluted the concept of "single responsibility" for both of these components.
It was also never super clear who should create the VM to be injected in to the VC either. The way I had been doing it, the ViewModel of the previous screen would create and configure a "Child View Model" to be injected into the ViewController that was being presented.. which was a bit messy, and unfortunately coupled the two screens together.
Enter stage: Coordinator 🎺
What the Coordinator pattern adds to this mix is the role of an overseeing "Coordinator" object (duh), responsible for a particular flow of your app, e.g. The Signup Flow.
It owns a canvas (a NavigationController, for example) onto which it can present ViewControllers as it wishes and, like a puppet master, sits high and mighty above the VC+VM layer pushing and pulling and popping screens into place, and responding to the output to decide what to do next. It is therefore the Coordinator's job to create both the VC and the VM, and to configure them as necessary.
Peeling away this responsibility from both the VC and the ViewModel really helps towards reaching the aims of the "Single Responsibility" principle.
In my opinion, the key benefits of MVVM boil down to the following:
- Separating the view model from the user interface makes it easier to test presentation logic.
- Separating the view controller from the presentation logic makes it easier to test the user interface.
Ash Furrow, "MVVM in Swift"
So, in terms of what the Coordinator pattern adds to this, I'd say:
Separating the VM and the VC from the responsiblity of managing navigation makes their actual roles in the architecture clearer and more specific. The implementation becomes dumber and reusability improves. Awareness of flow between screens (i.e. how you got to this screen and where to go next) is completly delegated upwards to the Coordinator - making everything simpler.
--
One last thing to add is that if you allow the VC to communicate with Blah VM only through a BlahViewModelType
protocol, you are then free to inject the VC with different implementations of the BlahViewModelType
for different purposes, making the VC still further reusable. For example, the MapViewController in Tacks is currently used in three different situations (once full-screen and twice as a child VC on other screens), simply by changing the underlying ViewModel to adjust what is shown.