Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coordinator and why it's the best reason to refactor your project

Coordinator and why it's the best reason to refactor your project

Talk by Мария Голубева

Поговорим об архитектурном паттерне Координатор, о его плюсах и минусах, когда стоит его имплементировать, а когда его использование будет оверинжинирингом, рассмотрим несколько вариантов его реализации на реальном проекте.

This talk was made for CocoaFriday #2 ( https://cocoaheads.org.ua/cocoafriday/2 ) which took place Apr 5, 2019.

Video: https://youtu.be/ewBChYlQtaQ

CocoaHeads Ukraine

April 05, 2019
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Technology

Transcript

  1. COORDINATOR
    AND WHY IT’S THE BEST REASON TO
    REFACTOR YOUR PROJECT
    Maria Holubieva, Software Engineer at Synergetica

    View Slide

  2. A long time ago in a galaxy far,
    far away…

    View Slide

  3. A long time ago in a galaxy far,
    far away…
    CLEAN
    ARCHITECTURE
    +
    MVP

    View Slide

  4. A long time ago in a galaxy far,
    far away…
    CLEAN
    ARCHITECTURE
    +
    MVP
    SOLID
    PRINCIPLES
    +
    TWO SENIOR
    DEVS

    View Slide

  5. A long time ago in a galaxy far,
    far away…
    CLEAN
    ARCHITECTURE
    +
    MVP
    SOLID
    PRINCIPLES
    +
    TWO SENIOR
    DEVS
    PROFIT

    View Slide

  6. CLEAN
    ARCHITECTURE
    +
    MVP
    SOLID
    PRINCIPLES
    +
    TWO SENIOR
    DEVS
    TYPICAL
    PROJECT*
    * with it’s own problems

    View Slide

  7. WHAT DID WE HAVE AT THE
    MIDDLE OF THE FIRST YEAR?

    View Slide

  8. WHAT DID WE HAVE AT THE MIDDLE OF THE FIRST YEAR?
    - NAVIGATION THROUGH ROUTER
    PRESENTER
    ROUTER
    Navigation
    between
    modules
    VIEW CONTROLLER
    data to display
    user actions
    MODEL
    request data

    View Slide

  9. WHAT DID WE HAVE AT THE MIDDLE OF THE FIRST YEAR?
    - NAVIGATION THROUGH ROUTER
    - SIMPLE ONE DIRECTIONAL APP FLOWS
    present push push
    dismiss

    View Slide

  10. WHAT DID WE HAVE AT THE MIDDLE OF THE FIRST YEAR?
    - NAVIGATION THROUGH ROUTER
    - SIMPLE ONE DIRECTIONAL APP FLOWS
    - PASSING DATA BETWEEN TWO NEIGHBOR
    MODULES
    LOGIN MODULE MAIN SCREEN
    MODULE
    current consumer

    View Slide

  11. AND IN 2 YEARS…

    View Slide

  12. AND IN 2 YEARS…
    COMPLEX NAVIGATION WITH MODULES REUSING IN
    THE DIFFERENT FLOWS
    SEARCH FOR
    SUBCATEGORY
    SEARCH FOR
    A TRAINER
    CHOOSE
    FITNESS
    ACTIVITY
    TYPE
    TRAINER
    PROFILE
    CREATE
    APPOINTMENT
    SEARCH FOR
    SUBCATEGORY
    TRAINER
    PROFILE
    for gym
    activity
    type
    for group
    activity type
    for any
    currently available
    activity type

    View Slide

  13. AND IN 2 YEARS…
    PRESENTER MUST KNOW THE FLOW TYPE
    SEARCH FOR
    GYM ACTIVITY
    SEARCH FOR
    GROUP ACTIVITY

    View Slide

  14. AND IN 2 YEARS…
    DIFFERENT DATA PASSING BETWEEN MODULES DEPENDS ON THE FLOW TYPE
    CHOOSE
    FITNESS
    ACTIVITY
    TYPE
    SEARCH FOR
    SUBCATEGORY
    TRAINER
    PROFILE
    flow type
    SEARCH FOR
    A TRAINER
    Gym activity
    • flow type
    • subcategory ID
    Group activity
    • flow type
    • trainer ID
    • flow type
    • trainer ID

    View Slide

  15. AND IN 2 YEARS…
    NEED TO STORE UNUSED DATA, REQUIRED FOR THE NEXT MODULES
    CHOOSE
    APPOINTMENT
    DATE & TIME
    CHOOSE
    FITNESS
    CLUB
    CHOOSE
    TRAINER
    APPOINTMENT
    SUMMARY
    • trainerID
    • trainerID
    • fintessClubID
    • trainerID
    • fintessClubID
    • choosedDate

    View Slide

  16. CONCLUSIONS
    OUR PROJECT PROBLEMS
    ▸ it’s hard to reuse Modules
    ▸ every Module knows about other Modules
    ▸ it’s hard to change Flows
    ▸ it’s hard to test Flows

    View Slide

  17. COORDINATOR
    AND WE FOUNT A GOOD SOLUTION

    View Slide

  18. WHAT IS COORDINATOR?
    COORDINATOR IS RESPONSIBLE FOR THE APPLICATION’S FLOW
    CHOOSE
    FITNESS
    ACTIVITY
    TYPE
    START GYM
    ACTIVITY
    COORDINATOR
    START GROUP
    ACTIVITY
    COORDINATOR
    START ANY
    ACTIVITY
    COORDINATOR

    View Slide

  19. WHAT IS COORDINATOR?
    THE COORDINATOR KNOWS NOTHING OF ITS PARENT
    COORDINATOR, BUT IT CAN ADD CHILD COORDINATORS
    Fitness activity coordinator
    SEARCH FOR
    SUBCATEGORY
    SEARCH FOR
    A TRAINER
    TRAINER
    PROFILE
    START
    add child
    create appointment
    coordinator

    View Slide

  20. IT IS CODING
    TIME…

    View Slide

  21. ▸ Create your own app starting point

    View Slide

  22. protocol Coordinator: class {
    func start()
    }
    ▸ declare Coordinator protocol

    View Slide

  23. class ApplicationCoordinator: Coordinator {
    private let window: UIWindow
    private let navigationController: UINavigationController
    init(window: UIWindow) {
    self.window = window
    navigationController = UINavigationController()
    }
    func start() {
    window.rootViewController = navigationController
    window.makeKeyAndVisible()
    showChooseActivityTypeScreen()
    }
    private func showChooseActivityTypeScreen() {
    let viewController = ChooseActivityTypeViewController()
    rootViewController.pushViewController(viewController,
    animated: false)
    }
    }
    ▸ implement ApplicationCoordinator class

    View Slide

  24. @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    private var applicationCoordinator: ApplicationCoordinator?
    func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions:
    [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let window = UIWindow(frame: UIScreen.main.bounds)
    let applicationCoordinator = ApplicationCoordinator(window: window)
    self.window = window
    self.applicationCoordinator = applicationCoordinator
    applicationCoordinator.start()
    return true
    }
    }
    ▸ start ApplicationCoordinator

    View Slide

  25. LET’S ADD
    MORE…

    View Slide

  26. protocol Coordinator: class {
    var onFinish: (() -> Void)? { get set }
    func start()
    }
    class BaseCoordinator {
    var childCoordinators: [Coordinator] = []
    func addDependency(_ coordinator: Coordinator) {
    for element in childCoordinators {
    if element === coordinator { return }
    }
    childCoordinators.append(coordinator)
    }
    func removeDependency(_ coordinator: Coordinator?) {
    guard childCoordinators.isEmpty == false,
    let coordinator = coordinator else { return }
    for (index, element) in childCoordinators.enumerated() {
    if element === coordinator {
    childCoordinators.remove(at: index)
    break
    }
    }
    }
    }
    ▸ add ability to build Coordinators hierarchy

    View Slide

  27. class ApplicationCoordinator: BaseCoordinator, Coordinator {
    private let window: UIWindow
    private let navigationController: UINavigationController
    init(window: UIWindow) {
    self.window = window
    navigationController = UINavigationController()
    }
    func start() {
    window.rootViewController = navigationController
    window.makeKeyAndVisible()
    startChooseActivityFlow()
    }
    private func startChooseActivityFlow() {
    let chooseActivityCoordinator = ChooseActivityTypeCoordinator(with:
    navigationController)
    chooseActivityCoordinator.onFinish = {
    [weak self, weak weakCoordinator = chooseActivityCoordinator] in
    self?.removeDependency(weakCoordinator)
    }
    addDependency(chooseActivityCoordinator)
    chooseActivityCoordinator.start()
    }
    }
    ▸ change ApplicationCoordinator

    View Slide

  28. enum ActivityType {
    case fitness
    case group
    case any
    }
    class ChooseActivityTypeViewController: UIViewController {
    //...
    var onActivityTypeSelected: ((_ type: ActivityType) -> Void)?
    //...
    }
    ▸ add callback to ChooseActivityTypeViewController

    View Slide

  29. class ChooseActivityTypeCoordinator: BaseCoordinator, Coordinator {
    var onFinish: (() -> Void)?
    private unowned let navigationController: UINavigationController
    init(with navigationController: UINavigationController) {
    self.navigationController = navigationController
    }
    func start() {
    showChooseActivityTypeScreen()
    }
    private func showChooseActivityTypeScreen() {
    let vc = ChooseActivityTypeViewController()
    vc.onActivityTypeSelected = { [weak self] (activityType: ActivityType) in
    switch activityType {
    case .fitness: self?.startFitnessActivityFlow()
    case .group: self?.startGroupActivityFlow()
    case .any: self?.startAnyActivityFlow()
    }
    }
    navigationController.pushViewController(vc, animated: false)
    }
    //...
    }
    ▸ create ChooseActivityTypeCoordinator

    View Slide

  30. //...
    private func startFitnessActivityFlow() {
    let fitnessCoordinator = FitnessActivityCoordinator(with: navigationController,
    appointmentsManager: appointmentsManager)
    fitnessCoordinator.onFinish = {
    [weak self, weak weakCoordinator = fitnessCoordinator] in
    self?.removeDependency(weakCoordinator)
    self?.navigationController.popToRootViewController(animated: true)
    }
    addDependency(fitnessCoordinator)
    fitnessCoordinator.start()
    }
    //...
    ▸ add in ChooseActivityTypeCoordinator

    View Slide

  31. MY EXAMPLE
    FULL PROJECT
    ▸ https://github.com/miserya/CoordinatorExample

    View Slide

  32. MORE COMPLICATED COORDINATOR CONFIGURATIONS
    YOUR
    COORDINATOR
    COORDINATORS
    FACTORY
    ROUTER
    INSTRUCTOR
    STATE
    STORAGE
    ask for the next
    module to present
    save data
    ask to present
    next module
    the chosen way
    ask to create
    a coordinator

    View Slide

  33. IS IT A PERFECT SOLUTION?
    COORDINATOR

    View Slide

  34. ACTUALLY NO
    CONS
    ▸ you need to write more code;
    ▸ it’s not a solution for a small projects;
    ▸ you need to observe NavigationController pop action on
    swipe or default Back button tap;

    View Slide

  35. BUT!
    PROS
    ▸ Coordinators keep the project straightforward and the
    Modules independent;
    ▸ simple Modules reusing;
    ▸ with Coordinators it’s easier to implement new features/
    flows/navigations;
    ▸ app Flows become easier to test;
    ▸ initial Project’s architecture does not matter;

    View Slide

  36. THANK YOU!
    USEFUL LINKS
    ▸ https://www.raywenderlich.com/158-coordinator-tutorial-
    for-ios-getting-started
    ▸ https://medium.com/blacklane-engineering/coordinators-
    essential-tutorial-part-i-376c836e9ba7
    ▸ https://medium.com/blacklane-engineering/coordinators-
    essential-tutorial-part-ii-b5ab3eb4a74

    View Slide