How to implement flow coordinator pattern
When I’ve started writing articles about iOS development, I wanted to write about topics that are hard to find. So I’ve written about custom transitions using coordinator pattern and how to implement delegation pattern using MVVM and coordinators.
One of the biggest complaints about the articles was that I didn’t explain what coordinator pattern is. Because there are a lot of great articles about this topic, Andrey Panov’s Coordinator Essential Tutorial, Krzysztof Zabtocki’s lecture about Good iOS Application Architecture, Denis Walsh’s Flow Coordinators in iOS. I thought that everyone is familiar with it, but I was wrong.
Why do we need coordinators?
When we talk about navigation, usually we think about something like this. Moreover, keep in mind that this is a good scenario. There are no if statements, animating transitions and passing data between controllers.
What is the problem with this approach?
- too much code in the controller
- every controller knows about other controllers
- it’s hard to make changes in flow
- the code is not reusable
- it’s hard to test
What is coordinator?
A coordinator is an object which handles app navigation flow and configures controllers and view models. Controllers don’t know anything about coordinator. They only expose an interface that informs the coordinator when navigation should happen.
We will create an app that has the usual stuff login, registration, changing password, profile, and some other controllers. For navigating we will use coordinator pattern and analyze the results.
Let’s see which coordinators our app needs:
- Application coordinator — every app needs this one. Application coordinator will start navigation flow and spawn new coordinators
- Auth coordinator
- Change password coordinator
- Main coordinator — handling app flow when the user is logged in
- Profile coordinator — handling user profile changes
By looking at this schema, we see that coordinators need to instantiate, add and remove other coordinators, instantiate controllers and to know how to navigate through the app. There is no one way of implementing this logic. As you will see my approach relies on Andrey Panov’s.
For instantiating coordinators we are using CoordinatorFactory.
In BaseCoordinator we have logic for adding and removing coordinators. ChildCoordinators array stores active coordinators. We need to have a strong reference to those, if not they will be removed from memory.
Look at app schema. If the user is on Profile VC, Profile coordinator is in Main coordinator’s childCoordinators variable. In the meanwhile Main coordinator is in Application coordinator’s childCoordinators variable. If we navigate back from Profile VC to A VC, the Main coordinator will remove Profile coordinator from the childCoordinators array, and we won’t have strong reference anymore.
For instantiating view controllers, we are using ViewControllerFactory. This class is quite simple. We use a factory pattern for creating controllers.
For routing, we are using a Router object. It has reference to navigation controller so the router can present, push, dismiss and pop controller for us. It also supports custom transitions.
Now our coordinator object has all it needs let’s try using it.
Implementing the coordinator
In the AppDelegate, we are going to initialize Application coordinator and set the root view controller. Application coordinator will decide should we go with auth or main flow using LaunchInstructor object. Because this is just proof of concept, we won’t implement it. However, the idea is that LaunchInstructor should check if auth token is available and if it is, then registration is completed.
When starting the app, we can log in or register. After we finish, the login process Application coordinator removes Auth and starts the Main coordinator.
What we got so far?
- There is less code in our controllers because our navigation logic is out.
- Controllers don’t know about other controllers.
Reusability
As you can see in the schema, we have change password coordinator in two places. If we want to reuse any flow, we need to create a new instance of selected coordinator, and everything will be ready to go.
Flow changes
How many times product manager came to you and said One small change. We just need to add walk-through and deep linking. Too many times if you ask me. Luckily this transition will be less painful now.
If we want to add walk-through, we need to create a new coordinator and to change LaunchInstructor object. We don’t need to change anything in app delegate or any view controllers.
Adding deep linking and push notifications to project can be very painful. However, flow coordinators have a solution for this too. When starting your coordinator, you’ll use startWithOption and create new flow or re-use existing one.
Testing
Our controllers and view models are now cleaner, more reusable and don’t have so many dependencies. So it’s easier for us to write tests. In this article, we won’t cover testing because that can be a whole new topic.
Conclusion
With flow coordinators, we’ve removed navigation logic from view controller. Our code is cleaner, reusable, more comfortable to change and more testable. At first, it may see that there is too much code to write. However, much code you write — BaseCoordinator, Router, LaunchInstructor you can reuse in other projects, so just for the first time, there is much work. Some people say it’s too complicated, I don’t think so. Even if that’s right, try it out. Its benefits are too good to miss. Here is the code example.
Resources
- Project example
- Custom transitions using coordinator pattern
- How to implement delegation pattern using MVVM and coordinators
- Andrey Panov’s Coordinator Essential Tutorial
- Krzysztof Zabtocki’s lecture about Good iOS Application Architecture
- Denis Walsh’s Flow Coordinators in iOS
Thanks for reading :)
If this article was helpful and you want to hear more about similar topics, please clap, share, follow or comment.