2 min read

Navigation & Routing

Last Updated - Platform 25.0 - SDK 20.0

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


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

Coordinators, within the Poq SDK, are more like feature specific navigators that handle navigation to that feature rather than coordinator for a specific instance of that feature. This ambiguity will be addressed in a future version of the SDK.

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.


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.


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.map(route: "something/:id") { route in
guard let id = route.routeParams["id"] else { return Log.error("Missing something id") }
let viewController = SomethingViewController(id: id)
NavigationHelper.shared.navigate(to: viewController, isModalInNavigation: route.isModal)

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.map(route: "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.map(route: "products/:product_id") { route 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 = route.isModal
let title = route.title


Objects can be passed when resolving routes as additional context to the navigation. This can be useful to avoid unnecessary loads by forwarding data or even the state to the feature.

let navigator = Container.shared.navigator()
navigator.resolve(deeplink, context: ["state": ProductState()])

These objects can be retrieved similarly to parameters, via the route.

Turnpike.map(route: "products/:product_id") { route in
let state = route.context?["product"] as? ProductState