-
Notifications
You must be signed in to change notification settings - Fork 3k
Navigation & Coordinators
Coordinators are the current pattern used in our application to navigate. 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 are stand alone in an app, unaware of their position in the 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. In other words, ViewController A
is not aware it's presenting ViewController B
, and ViewController B
doesn't know it can also be presented from ViewController C
.
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 was also deciding what is shown and when, on top of holding a reference to all the views. Navigation code was sometimes duplicated, since each navigation path was coupled with the previous navigation or view controller. Unit tests were non-existent for any navigation path. 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 increased our development time and decrease the maintainability of our application. BrowserViewController
was also hiding and showing the homepage in a way that isn’t a standard iOS pattern. This introduced a number of issues when adding features on the homepage, since standard lifecycle methods weren’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.
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 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.
- We do have exceptions for this in our code, but coordinators shouldn't hold references to the shown view controllers. Unless there's specific reasons to do so, please avoid keeping a reference to the view controller.
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 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).
homepageViewController
refers to thelegacyHomepageViewController
here.
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.
Note that browserViewController
and homepageViewController
are kept as strong reference under the BrowserCoordinator
because:
- The
BrowserCoordinator
needs to embed the webview inside theBrowserViewController
. TheWebViewViewController
cannot be removed entirely from the view hierarchy since the web view cannot reload properly the webpage otherwise. - The
HomepageViewController
is kept as a reference since its data is not part of redux. Each time theHomepageViewController
is created, the data is fetched again from network. To ensure the loading of the homepage is not expensive, its kept as a reference under the browser coordinator.
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.