-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Navigation & Coordinators
One of the challenges of working in Firefox for iOS is making changes inside our massive class called BrowserViewController. This class handles and holds pretty much everything of what makes Firefox iOS the application it is. It decides what is showing and when, and it holds a reference to all the views. Navigation code is sometimes duplicated, since each navigation path is coupled with the previous navigation or view controller. It is also barely tested. The consequence of that is anytime we need to make changes to navigation code, repercussions often happens in production. Some navigation path are intricate to test and requires deep knowledge of the system. This increase our development time and decrease the maintainability of our application. BrowserViewController also hides and show the homepage in a way that isn’t a standard iOS pattern. This has introduced a number of issues when adding features on the homepage, since standard lifecycle methods aren’t called on it.
As a first step of reducing the scope of the BrowserViewController
and fixing the appearance of the homepage, coordinators were chosen as a pattern to fix those problems.
Coordinators are a specialized type of delegate that lets us remove the job of app navigation from our view controllers. They’ve been around for a while (since 2015) and are used in a myriad of applications. They help to make our view controllers more manageable and reusable, while also letting us adjust our app's flow whenever we need. View controllers work best when they stand alone in our app, unaware of their position in our app’s flow, or even that they are part of a flow in the first place. Not only does this help make our code easier to test and reason about, but it also allows you to re-use view controllers elsewhere in our app more easily.
There are different ways of implementing the coordinator pattern inside an application. Common solutions to pitfalls were investigated in the document here. The recommended solution after investigation is the Router pattern, which is a class that wraps a UINavigationController
to pass it around between coordinators.
The Router is a class that wraps a UINavigationController to pass it around between coordinators. We extend a Router class to conform to UINavigationControllerDelegate
, so the router can handle all the events for the navigation controller. It wraps and delegate the responsibility for what to do on the back button event back to the coordinator that pushed this coordinator in the first place.
To ensure we don’t end up with massive coordinators and properly remove tight coupling between view controllers, we should follow some principles.
- Coordinator should control the flow in our app, which means creating VCs and view models, passing data and state (unless we have external state handling, like the proposed Redux solution) and calling the router to present and push. It shouldn’t decide what to show or contain business logic. The one exception for this is that coordinator can contain A/B tests variants since it’s convenient.
- Coordinator should manage only view controllers.
This section explains the architecture implemented for v114. There will be subsequent work coming to introduce the Coordinator pattern even more in our application. The documentation will be updated once the subsequent work is done and released.
The following coordinators are currently implemented. Each Scene holds its own SceneCoordinator
.
At launch of the application, we now have a second LaunchScreen
that helps us determine into which path we should fall into. This launch screen appears for a split second, and is there basically to asynchronously determine if the SceneCoordinator
should present different onboarding or navigate to the browser directly. The following image illustrate what screens appears to the user when we launch the application on first app install (it shows the onboarding).
The BrowserCoordinator
is currently our central piece. More will be handled in other coordinators in the future, but for now this coordinator holds the browserViewController
and homepageViewController
, as well as managing almost all the deep links (more on that in the next section). The BrowserCoordinator
is responsible for embedding the home page and web view inside a container, successfully removing this responsibility that was previously hold by the browserViewController
. BrowserViewController
isn't directly aware of what the container holds.
The SceneDelegate receives connecting options on its delegate method from different user actions. It can either be of URL format, user activity format or shortcut format. Those options gets parsed as one of our Route
enum case, which is then passed down from SceneCoordinator to child coordinators to be handled so we navigate to the proper screen being asked by the user action.