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

Desacoplando fluxos - Coordinators e Rotas

Desacoplando fluxos - Coordinators e Rotas

Como criamos abstrações de navegação e aumentamos a possibilidade de reuso com Coordinators e Rotas no Nubank

Avatar for Victor Maraccini

Victor Maraccini

November 09, 2018

More Decks by Victor Maraccini

Other Decks in Programming

Transcript

  1. Nubank App Motivação App Home Cartão de Crédito NuConta Rewa

    UIViewController UIViewController UIViewController Fluxos & Bibliotecas Rotas
  2. Necessidade • Abrir fluxos de qualquer lugar do app •

    Após separarmos em bibliotecas, não podíamos ter acoplamento • Ser dinâmico e responder ao backend • Poder alterar o comportamento sob demanda
  3. Como resolver? • Um único ponto de entrada • URLs

    para identificar um fluxo • Tudo passado por parâmetro na query • Ainda manter composição • Saber quando uma rota começa, produz efeitos colaterais e termina
  4. RoutingHub Porta de entrada para abrir um fluxo routingHub .start(url:

    URL(string: "nuapp://domain")) Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  5. RoutingHub Porta de entrada para abrir um fluxo routingHub .start(url:

    URL(string: "nuapp://domain")) .subscribe(onNext: { state in //React to state changes } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  6. RoutingHub Porta de entrada para abrir um fluxo routingHub .start(url:

    URL(string: "nuapp://domain")) .subscribe(onNext: { state in //React to state changes }, onError: { error in //Handle flow errors } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  7. RoutingHub Porta de entrada para abrir um fluxo routingHub .start(url:

    URL(string: "nuapp://domain")) .subscribe(onNext: { state in //React to state changes }, onError: { error in //Handle flow errors }, onCompleted: { //Perform finishing actions }) Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  8. RoutingHub Interface RxSwift para fluxos no app class RoutingHub {

    func start(url: URL) -> Observable<RoutingState> { } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  9. RoutingHub Interface RxSwift para fluxos no app class RoutingHub {

    func start(url: URL) -> Observable<RoutingState> { guard let router = self.router else { return .error(RoutingError.routerUnavailable) } } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  10. RoutingHub Interface RxSwift para fluxos no app class RoutingHub {

    func start(url: URL) -> Observable<RoutingState> { guard let router = self.router else { return .error(RoutingError.routerUnavailable) } guard let descriptor = descriptors.first { $0.match(url: url) } else { return .error(RoutingError.unknownRoute) } } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  11. RoutingHub Interface RxSwift para fluxos no app class RoutingHub {

    func start(url: URL) -> Observable<RoutingState> { guard let router = self.router else { return .error(RoutingError.routerUnavailable) } guard let descriptor = descriptors.first { $0.match(url: url) } else { return .error(RoutingError.unknownRoute) } return router.start(url: url, descriptor: descriptor) } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  12. RoutingHub Registro de rotas em Runtime class RoutingHub { var

    router: Router? var descriptors: [RouteDescriptor] = [] func register(descriptor: RouteDescriptor) func register(router: Router) … } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  13. RoutingHub Registro de rotas em Runtime routingHub.register(descriptor: rewardsRoute) routingHub.register(descriptor: chatRoute)

    routingHub.register(descriptor: cardActivationRoute) routingHub.register(descriptor: helpRoute) … routingHub.start(url: URL(string: "nuapp://domain")) Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  14. RouteDescriptor Responsável por descrever uma rota e expor seu ciclo

    de vida protocol RouteDescriptor: AnyObject { func match(url: URL) -> Bool func start(url: URL, on context: UIViewController) -> Observable<RoutingState> } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  15. RouteDescriptor Exemplo class SomeRouteDescriptor: RouteDescriptor { func match(url: URL) ->

    Bool { return url.host == "domain" //Matches xxx://domain } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  16. RouteDescriptor Exemplo class SomeRouteDescriptor: RouteDescriptor { func match(url: URL) ->

    Bool { return url.host == "domain" //Matches xxx://domain } func start(url: URL, on context: UIViewController) -> Observable<RoutingState> { return Observable.create { (observer) -> Disposable in } } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  17. RouteDescriptor Exemplo class SomeRouteDescriptor: RouteDescriptor { func match(url: URL) ->

    Bool { return url.host == "domain" //Matches xxx://domain } func start(url: URL, on context: UIViewController) -> Observable<RoutingState> { return Observable.create { (observer) -> Disposable in let coordinator = SomeCoordinator(context: context) do { try coordinator.start(onCompleted: observer.onCompleted) } catch { observer.onError(error) } } } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  18. RouteDescriptor Exemplo class SomeRouteDescriptor: RouteDescriptor { func match(url: URL) ->

    Bool { return url.host == "domain" //Matches xxx://domain } func start(url: URL, on context: UIViewController) -> Observable<RoutingState> { return Observable.create { (observer) -> Disposable in let coordinator = SomeCoordinator(context: context) do { try coordinator.start(onCompleted: observer.onCompleted) } catch { observer.onError(error) } return Disposables.create { coordinator.dismiss() } } } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  19. RoutingState Descrevendo o estado de fluxos func start(url: URL) ->

    Observable<RoutingState> enum RoutingState { } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  20. RoutingState Descrevendo o estado de fluxos func start(url: URL) ->

    Observable<RoutingState> enum RoutingState { case preparing } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  21. RoutingState Descrevendo o estado de fluxos func start(url: URL) ->

    Observable<RoutingState> enum RoutingState { case preparing case started } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  22. RoutingState Descrevendo o estado de fluxos func start(url: URL) ->

    Observable<RoutingState> enum RoutingState { case preparing case started case produced } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  23. Coordinator Abstração de fluxo class SomeCoordinator { var firstScreen: FirstViewController?

    var secondScreen: SecondViewController? func start(onCompleted: () -> Void) { } func dismiss() { } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  24. Coordinator Abstração de fluxo class SomeCoordinator { ... func start(onCompleted:

    () -> Void) { let firstScreen = FirstViewController(nextPressed: { [weak self] userInput in }) self.firstScreen = firstScreen context.present(firstScreen, animated: true, completion: nil) } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  25. Coordinator Abstração de fluxo class SomeCoordinator { ... func start(onCompleted:

    () -> Void) { let firstScreen = FirstViewController(nextPressed: { [weak self] userInput in self?.firstScreenFinished(withInput: userInput) }) self.firstScreen = firstScreen context.present(firstScreen, animated: true, completion: nil) } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  26. Coordinator Abstração de fluxo class SomeCoordinator { ... func start(onCompleted:

    () -> Void) { let firstScreen = FirstViewController(nextPressed: { [weak self] userInput in self?.firstScreenFinished(withInput: userInput) }) self.firstScreen = firstScreen context.present(firstScreen, animated: true, completion: nil) } func firstScreenFinished(withInput input: String) { let secondScreen = SecondViewController(previousInput: input, nextPressed: { userInput in /*…*/ }) self.secondScreen = secondScreen firstScreen?.present(secondScreen, animated: true, completion: nil) } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController
  27. Coordinator Abstração de fluxo class SomeCoordinator { ... func start(onCompleted:

    () -> Void) { let firstScreen = FirstViewController(nextPressed: { [weak self] userInput in self?.firstScreenFinished(withInput: userInput) }) self.firstScreen = firstScreen context.present(firstScreen, animated: true, completion: nil) } func firstScreenFinished(withInput input: String) { let secondScreen = SecondViewController(previousInput: input, nextPressed: { userInput in /*…*/ }) self.secondScreen = secondScreen firstScreen?.present(secondScreen, animated: true, completion: nil) } func dismiss() { firstScreen?.dismiss(animated: true, completion: nil) } } Router Fluxo RoutingHub RouteDescriptor Coordinator UIViewController UIViewController UIViewController UIViewController