Skip to content

Routing

Giuseppe Lanza edited this page Aug 26, 2018 · 6 revisions

Routing in this architectural model is a plug-in implemented through events listener. Whenever an event can be transformed as a routing event the RoutingEventsListener interested in that particular event will transform the event into a routing step. Each RoutingEventsListener has a reference to a Router that then will be able to transform the RoutingStep into an actual UI action (push, present and so on).

Routers are the only objects that have a strong connection with the module manager so that they can ask it for a viewController to perform a specific routing step.

Routers have references to the module manager, but this still happens through abstraction; in fact Routers use ViewControllerFactory. (Routers do not know about the existence of modules)

When a router is asked to perform a routing step, the router will ask the module manager to transform the routing step into a viewController they can use to push/present on screen.

Routing Flow

Module Routing step

We must not forget that routing is something that cannot be abstracted from the fact that a screen exists. On import of a specific module, the existence of this module is exposed to the rest of the main target via an extension of ModuleRoutingStep in which a static function will be responsible for building a ModuleRoutingStep instance with the right module making object.

Example:

ProductDetailPageStep.swift

import ProductDetailPageModule //In an healthy app this should be the only time where the import for this module is made

typealias PDPEvents = ProductDetailPageEvents

extension ModuleRoutingStep {
    static func product(routingContext: RoutingContext = .mainFlow, id: String, product: ProductProtocol?) -> ModuleRoutingStep {
        return ModuleRoutingStep(withMaker: PDPMaker(routingContext: routingContext.rawValue, productId: id, product: product))
    }
}

//PDPMaker is an object defined within the ProductDetailPageModule

The module manager will know how to use a ModuleRoutingStep to build a module, extract its rootViewController and return it to the router.

Presentable Routing Step

For routing purposes a module routing step is not enough for the router to understand the behaviour the new controller should have in presentation. For this purpose, the module Routing Step must be wrapped into a PresentableRoutingStep. This object will simply contain the preferences in presenting the new view controller on screen.

public struct PresentableRoutingStep {
    public let step: ModuleRoutingStep
    public let presentationMode: RoutingStepPresentationMode
    public let modalPresentationStyle: UIModalPresentationStyle
}

The RoutingStepPresentationMode is what helps the router to understand the presentation behaviour.

public enum RoutingStepPresentationMode {
    case push(withCloseButton: Bool, onClose: (()->())?)
    case modal
    case modalWithNavigation(withCloseButton: Bool, onClose: (()->())?)
}

Router

Router in MERLin' is a protocol. This protocol has protocol extensions to provide default implementation for the methods

@discardableResult func route(to destination: PresentableRoutingStep) -> UIViewController?
@discardableResult func route(toDeeplink deeplink: String) -> UIViewController?

we leave to the concrete implementation to define how to handle shortcuts and the rootViewController to be used as root of the window in the app delegate.

The default implementation of the routing functions will inspect the viewController stack and will try to fulfil the preferences expressed in the presentableRoutingStep. The success of this attempt depends obviously from the view stack. If you try to push a view controller while the top view controller is not an UINavigationController the router will present the new ViewController modally as fallback.

The router protocol extension also provides a default behaviour for Deeplinking.

Clone this wiki locally