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

Db84cf61fdada06b63f43f310b68b462?s=128

CocoaHeads Ukraine

April 05, 2019
Tweet

Transcript

  1. 1.

    COORDINATOR AND WHY IT’S THE BEST REASON TO REFACTOR YOUR

    PROJECT Maria Holubieva, Software Engineer at Synergetica
  2. 3.
  3. 4.

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

    CLEAN ARCHITECTURE + MVP SOLID PRINCIPLES + TWO SENIOR DEVS
  4. 5.

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

    CLEAN ARCHITECTURE + MVP SOLID PRINCIPLES + TWO SENIOR DEVS PROFIT
  5. 6.

    CLEAN ARCHITECTURE + MVP SOLID PRINCIPLES + TWO SENIOR DEVS

    TYPICAL PROJECT* * with it’s own problems
  6. 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
  7. 9.

    WHAT DID WE HAVE AT THE MIDDLE OF THE FIRST

    YEAR? - NAVIGATION THROUGH ROUTER - SIMPLE ONE DIRECTIONAL APP FLOWS present push push dismiss
  8. 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
  9. 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
  10. 13.

    AND IN 2 YEARS… PRESENTER MUST KNOW THE FLOW TYPE

    SEARCH FOR GYM ACTIVITY SEARCH FOR GROUP ACTIVITY
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 28.

    enum ActivityType { case fitness case group case any }

    class ChooseActivityTypeViewController: UIViewController { //... var onActivityTypeSelected: ((_ type: ActivityType) -> Void)? //... } ▸ add callback to ChooseActivityTypeViewController
  21. 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
  22. 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
  23. 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
  24. 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;
  25. 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;