Skip to content

Navigation & Coordinators

Matt Lichtenstein edited this page Jul 12, 2024 · 3 revisions

Motivation

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.

Why coordinators

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.

Pattern choice

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.

What should a Router do

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.

What should a Coordinator do

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.

Architecture implemented

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.

Overview

The following coordinators are currently implemented. Each Scene holds its own SceneCoordinator.

Screenshot 2023-04-25 at 4 19 01 PM

Launch

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).

Screenshot 2023-04-25 at 4 15 18 PM

BrowserCoordinator

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.

Deep links

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.

Clone this wiki locally