2 min read

Navigation & Routing

Last Updated - Platform 22.0.0 - SDK 17.0.0

Navigation within the PoqSDK is mostly handled via deeplinks, or in some cases directly.

Coordinators

Coordinators are new in v22. They create and handle the navigation to (and of) a specific feature. They were added to hide the need to write code to inject routes; which can be messy and hard to find or understand.

The PoqSDK automatically sets up features, with no code, by injecting hardcoded deeplink routes on app launch. Coordinators use the main navigator to handle navigation to and from the view controller.

A Navigator is used to handle deeplink resolution and presenting view controllers. The main navigator is used via Container.shared.navigator() which defaults to the PoqPlatform's NavigationHelper.

Routes can be resolved using navigator.resolve("route") which executes the handler of that route to navigate to that feature. The PoqSDK supplies a convenience function for working with optional query params when creating a route.

let navigator = Container.shared.navigator()
navigator.resolve(.route("something/\(id)", params: [
"title": titleOrNil
]))

Use navigate(to:), present, presentNavigationController(with: or push to handle direct navigation to a view controller from within a custom route's closure.

The NavigationHelper is a legacy singleton mega-class that sets up all of the PoqPlatform's features by injecting their routes on app launch. It also handles building the NavigationController and resolving external deeplinks via the PoqPlatformModule.

This will eventually be removed or improved into the PoqSDK.

MockNavigator

For testing, the PoqSDK provides the MockNavigator to allow you to inject view controllers for routes that are simple matches using contains. Inject closures into the MockNavigator/viewControllers dictionary to handle your test routes.

From v23 you can set the MockNavigator/resolveUsingTurnpike to true to additionally resolve deeplinks via the SDK's defaults for UI testing.

Routes

Under the hood the PoqSDK stores a dictionary of deeplink route strings against their handling closures. Deeplink routes are set up in a similar way to containers; closures are injected for parameterised URL-based routes. When the route is resolved (navigated to) the closure is called with the passed path and query parameters.

Turnpike.mapRoute("something/:id") { request in
guard let id = request?.routeParams["id"] else { return Log.error("Missing something id") }
let viewController = SomethingViewController(id: id)
NavigationHelper.shared.navigateTo(viewController, isModalInNavigation: request.isModal)
.withTitle(request?.queryParams?["title"])
}

The PoqPlatform and PoqSDK use hardcoded deeplink routes for their features. These are specified in the same files as the coordinators or as static variables within the NavigationHelper.

Route Parameters

Dynamic URL component values (for example 132 in products/132) can be pulled from deeplinks being handled by the route's closure.

Turnpike.mapRoute("products/:product_id") { (route) in
guard let productId = route?.routeParameters?["product_id"] else { return }
}

Query Parameters

Query parameters can be added to pass additional identifers or optional modifiers. They are retrieved in a similar way to route parameters.

Turnpike.mapRoute("products/:product_id") { request in
let variantId = route?.queryParameters?["variant_id"]
}

Additionally, we commonly use is_modal and title query parameters within the NavigationHelper. These parameters have their own convenient accessors.

let isModal = request.isModal
let title = request.title

Objects can be passed when resolving deeplinks; however, this can be clunky and so is not advised. Instead, share data via a repository or store and passing the identifier to it as a route or query parameter.

NavigationHelper.shared.resolve(deeplink, context: ["object": SomeObject()])
NavigationHelper.shared.navigationContext = [:]

These objects can be retrieved in the same way where the context is cleared after the route is handled.

let product = NavigationHelper.shared.navigationContext?["product"] as? Product