Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Learning Topic: Coordinator Pattern #1

Open
b099l3 opened this issue Jun 22, 2021 · 4 comments · May be fixed by #4
Open

Learning Topic: Coordinator Pattern #1

b099l3 opened this issue Jun 22, 2021 · 4 comments · May be fixed by #4
Assignees
Labels
learning This indicates a learning topic
Milestone

Comments

@b099l3
Copy link
Owner

b099l3 commented Jun 22, 2021

Topic: Coordinator Pattern

Coordinator pattern created by Soroush Khanlou from a series of articles and a talk given at NSSpain

Example protocol for the most basic Coordinator:

protocol Coordinator {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }

    func start()
}

This is a good starting point for the protocol but not every project uses this protocol. I will need to assess other example protocols to see why they have changed parts of this.

Why:

This pattern is to:

  • Remove coupled navigation code in your ViewControllers
  • Increase reusability and give single responsibility to the coordinator
  • Reduce the complexity in ViewControllers
  • Allow for easier testing
  • Allow for easier changes to navigation flow

Here is a simple example,

public class HomeViewController: UIViewController {

    // ... Other code for View Controller

    @IBAction internal func didPressButton(_ sender: AnyObject) {
        // HomeViewController needs to know about AwayViewController and how to navigate to it.
        if let awayViewController = storyboard?.instantiateViewController(withIdentifier: "AwayViewController") {
            navigationController?.pushViewController(awayViewController, animated: true)
        }
    }
}

with the coordinator pattern and delegation this can result in:

// Coordinator implements this delegate
public protocol HomeViewControllerDelegate: class {
  func homeViewControllerDidPressButton(_ viewController: HomeViewController)
}

public class HomeViewController: UIViewController {
    public weak var delegate: HomeViewControllerDelegate?

    // ... Other code for View Controller

    @IBAction internal func didPressButton(_ sender: AnyObject) {
        // HomeViewController using a delegate
        // Removed coupling between HomeViewController and AwayViewController
        // also removed the navigation concerns away from HomeViewController 
        // as the delegate can decide how it should be navigated to, e.g. modal, push, pop, etc
        delegate?.homeViewControllerDidPressButton(self)
    }
}

This is a much cleaner approach as we can reuse the HomeViewController in other places within our app or swap out its coordinator to change the navigation in homeViewControllerDidPressButton

Notes:

This pattern has been around since 2015 so a lot has progressed since then, prepare to find it has evolved.

Some seem to use Delegates for events between Coordinator and ViewControllers others use Closures, need to see why this is the case?

Using closures with the coordinator pattern

That allows to inject callbacks into a view controller rather than a specific coordinator. This has the effect of creating even more loose coupling than having a coordinator object or protocol, allowing us to replace coordinators with something else entirely if we wanted.

This technique of closure injection is nothing new – in fact, Apple even mentioned it in their WWDC 2016 presentation, Improving Existing Apps with Modern Best Practices. However, I think it has its limits: if you try to pass in more than a couple of closures it gets messy, and you should consider using a coordinator object or protocol instead.

Possibly use both?

Why do some of the examples use Factories?

Resources:

Articles:

https://www.raywenderlich.com/books/design-patterns-by-tutorials/v3.0/chapters/23-coordinator-pattern
https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-i-376c836e9ba7
https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-ii-b5ab3eb4a74
https://pavlepesic.medium.com/flow-coordination-pattern-5eb60cd220d5 - references Andrey's Coordinator and others

https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps
https://wojciechkulik.pl/ios/mvvm-coordinators-rxswift-and-sample-ios-application-with-authentication
https://mattwyskiel.com/posts/2016/07/20/protocol-oriented-app-coordinator-swift.html

https://medium.com/devexperts/real-world-example-of-using-coordinator-pattern-in-an-ios-app-d13df10496a5

https://benoitpasquier.com/coordinator-pattern-navigation-back-button-swift/

Videos:

Examples:

https://github.com/AndreyPanov/ApplicationCoordinator

https://github.com/daveneff/Coordinator
https://github.com/igorkulman/iOSSampleApp
https://github.com/wojciech-kulik/Swift-MVVMC-Demo

https://github.com/hanifsgy/Journal-MVVMCRxSwift
https://github.com/behrad-kzm/SpotifyExplorer
https://github.com/omerfarukozturk/MovieBox-iOS-MVVMC
https://github.com/sunshinejr/Kittygram

SwiftUI
https://github.com/rundfunk47/stinsen
https://github.com/SwiftUIX/Coordinator

@b099l3 b099l3 added the learning This indicates a learning topic label Jun 22, 2021
@b099l3 b099l3 added this to the MVP milestone Jun 22, 2021
@b099l3 b099l3 self-assigned this Jun 22, 2021
@b099l3
Copy link
Owner Author

b099l3 commented Jun 24, 2021

Andrey Panov Coordinator

After doing some reading I quite like the approach taken in these articles by Andrey Panov:
https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-i-376c836e9ba7
https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-ii-b5ab3eb4a74

He also describes this pattern in a way that it could be used in MVVM, MVC, VIPER, etc, by calling the VM, VC, IPV - "modules"

Sample code here, it also has tests 🤘:
https://github.com/AndreyPanov/ApplicationCoordinator

Why should we use it?

The main goal is to have unchained module-to-module responsibility and to build modules completely independently from each other. After this iteration, we can easily reuse them in different flows.

He uses a similar protocol to the one mentioned above but simpler:

protocol Coordinator: class {
    func start()
    func start(with option: DeepLinkOption?) // This is used for deep linking
}

He has extracted the UINavigationController out of the coordinator and into an object called Router (similar to the RayWenderlich approach and others). I like this idea as it has serves the single responsibility principle, and I can see things getting messy if not extracted out.

He has also moved the childCoordinators into a baseCoordinator so that

So this example's coordinator is composed of two protocols, Coordinator & CoordinatorOutput, to allow for optional results after a flow has finished.

protocol CoordinatorOutput {
    var finishFlow: (Item -> Void)? { get set }
}

Terminology

using "show" prefix in coordinator methods when moving to another module, e.g.,

func showItemList()

using "run" prefix in coordinator methods when moving to another flow/coordinator, e.g.,

func runCreationFlow() 

Summary

The architecture elements proposed here are:

  • Coordinators for reasons similar to the description above
    • Remove coupling of Navigation and Flow within View Controller
    • Coordinators use Routers, Instructors, Factories and Storage
  • Routers
    • For Single Responsibility principle: coordinator manages flow but router manages navigation
    • Navigation for different devices, like iPhone, iPad, TV, or Watch
  • Instructors
    • To instruct the coordinator where to flow to, e.g., App start logic or Deeplinks
  • Factories
    • For creating coordinators and modules to allow them to be mocked in tests
    • Creates all objects, sets all dependencies, injects properties, and returns protocol
      • Modules’ factory, for creating modules and injecting all dependencies.
      • Coordinators’ factory (optional), in case we need to switch to a different flow.
  • Storage
    • Storage (optional), only if we store any data and inject it into the modules.
  • Uses a single storyboard for each area
  • Uses Closures for events between Coordinator and ViewController

I feel this approach has a lot of fo flexibility and looks easy to extend for other cases in navigation flow. I am going to start with this approach and feedback and issues.

Pavle Pesic has blogged about using this pattern How to implement flow coordinator pattern
and has some sample code

@b099l3 b099l3 changed the title Coordinator Pattern Learning Topic: Coordinator Pattern Jun 24, 2021
@b099l3
Copy link
Owner Author

b099l3 commented Jun 27, 2021

iOS Academy Coordinator

Video: Swift Coordinator Design Pattern (iOS, Xcode 12, 2021) - iOS Design Patterns

Protocol

protocol Coordinator {
    var navigationcontroller: UINavigationController? { get set }

    funceventOccurred(with type: Event)
    func start()
}

protocol Coordinating {
    var coordinator: Coordinator? { get set }
}

enum Event {
    case buttonTapped
}

Summary

  • Not extracted out the UINavigationController
  • Using an event enum to relay events - I don't think there is much gained from this, possibly due to having to use a protocol or coordinating.
  • Using the Coordinating protocol to allow Viewcontrollers to have a reference to their coordinators, others have used delegates or other methods for the comms between VC and coordinators.
  • Shows set up with SceneDelegate easy
  • Talks about how Facebook uses this pattern for the new messenger app (Jan 7, 2021)
  • Talks about how the Coordinator could have the API dependencies on it rather than the ViewController
  • Coordinator is not just navigation. Could be Networking, etc
  • doesn't use them for personal projects but does for his work and commercial-grade projects, useful for scaling
  • Doesn't use storyboards or xibs, all code

@b099l3
Copy link
Owner Author

b099l3 commented Jun 27, 2021

James Haville Coordinator

Video: MVVM iOS App Part 4: View | Event Countdown App

Set of tutorials on creaditing MVVM architecture with Coordinator

Protocol

protocol Coordinator {
    var childCoordinators: [Coordinator] { get }
    func start()
}

Summary

  • Used to separate navigation code from ViewController
  • Has an App Coordinator - sets rootViewcontroller init's with window - init(window:)
  • Can mix and match where we want to use coordinators
  • Has the navigation controllers created within the Coordinators, or passed with init(navigationController:)
  • Uses SceneDelegate
  • Is a simpler version than Andrey Panov's version possible due to trying to fit it into a YouTube video, e.g. no base coordinator, storyboard instantiate extension, factories, etc
    • Adds storyboard instantiate extension later in video 3
  • Uses CoreDate in the next episode part 2
  • Part 3 adds ViewMdoels in coordinator and passes them to the Viewcontrollers
  • also shows how to call Coordinator events
    • Viewmodels have a reference to Coordinators - so when buttons are tapped they go to the ViewModel then call the Coordinator
    • Talks about having a call back instead to avoid this reference
  • Uses a single storyboard for all views
  • Shows deinits for clearing up VM, VC and Coordinators
  • Shows Parent Coordinator for showing coordinator is finished

@b099l3
Copy link
Owner Author

b099l3 commented Jun 28, 2021

Ray Wenderlich Coordinator

Article: Coordinator Pattern

Sample Code

Protocol

public protocol Coordinator: class {
  var children: [Coordinator] { get set }
  var router: Router { get }

  func present(animated: Bool, onDismissed: (() -> Void)?)
  func dismiss(animated: Bool)
  func presentChild(_ child: Coordinator,
                    animated: Bool,
                    onDismissed: (() -> Void)?)
}

When to use it?

Use this pattern to decouple view controllers from one another. The only component that knows about view controllers directly is the coordinator.

Consequently, view controllers are much more reusable: If you want to create a new flow within your app, you simply create a new coordinator!

Summary

  • Uses Routers like Andrey's Coordinator Pattern
    • Has an AppDelegateRouter for setting up the window and rootviewcontroller
  • Uses extension methods for storyboard instantiable
  • Uses a storyboard for each view
  • Uses Delegates for events between Coordinator and ViewController

@b099l3 b099l3 linked a pull request Jul 1, 2021 that will close this issue
@b099l3 b099l3 linked a pull request Jul 9, 2021 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
learning This indicates a learning topic
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant