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

Organização e Escalabilidade do app Nubank

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Organização e Escalabilidade do app Nubank

Mostraremos a arquitetura utilizada no Nubank para o desenvolvimento do aplicativo iOS nos seguintes aspectos:

- Criação de telas: como dividir e organizar o código para criar componentes reutilizáveis e de fácil manutenção
- Uso de Reactive Extensions para construir os fluxos de informação do aplicativo
- Integração entre a lógica de negócios e a lógica de apresentação

Avatar for Victor Maraccini

Victor Maraccini

April 18, 2018

More Decks by Victor Maraccini

Other Decks in Programming

Transcript

  1. ViewController Controller Cria Ações View ViewModel Controle Ações Atualiza Controller:

    • Tratamento de ações • Desacoplamento do layout - Resultado do Controller não depende da tela • Criação da representação da View - Conversão de entidades para representações visuais - ViewModel: Dado puro
  2. Coordinator ViewController Controller Cria Ações Ações Atualiza View ViewModel Controle

    Ações Atualiza • Recebe resultado do Controller • Provê dados para o Controller - Une dados de diferentes fontes para dá-los ao Controller - Pega dados de diferentes fontes (ex: API) para montar o contexto • Coordena mutações nos modelos - Não modifica diretamente os modelos Coordinator:
  3. Coordinator ViewController Controller Cria Ações View ViewModel Manager Modelo Atualiza

    Controle Ações Atualiza Atualiza Ações • Fornece o modelo • Porta de entrada para atualizações - Coordena requests para API - Conhece as entidades e mutações possíveis Manager:
  4. Coordinator ViewController Controller Cria Ações View ViewModel Manager Connector Store

    Modelo Atualiza API Disco Controle Ações Atualiza Atualiza Ações
  5. Coordinator ViewController Controller Cria Ações View ViewModel Manager Connector Store

    Modelo Atualiza API Disco Controle Ações Atualiza Atualiza Ações
  6. Coordinator ViewController Controller Cria Ações Ações Atualiza View ViewModel Ações

    Atualiza Modelo Atualiza Controle Manager Connector Store API Disco Apresentação Negócios
  7. Rx • Extensões Reativas: Uma biblioteca para compor programas assíncronos

    e baseados em eventos usando sequências observáveis The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences… https://github.com/ReactiveX/RxSwift https://github.com/Reactive-Extensions/Rx.NET • Usando Rx, é possível representar sequências assíncronas de dados com e pegar sequências assíncronas com Using Rx, developers represent asynchronous data streams with Observables, query asynchronous data streams [with Observers] Observables Observers
  8. Observable Observer Rx: composição .map() .filter() • Criar a sequência

    de eventos • Aplicar funções na sequência • Realizar operações
  9. Aplicação • Validação de input - Texto fica vermelho se

    CPF estiver incorreto (após primeira validação) - Botão para prosseguir só aparece se CPF estiver correto • Layout: Label, Input de texto e botão
  10. View class InputView: View { } let titleLabel = UILabel(numberOfLines:

    0) let textField = MaskedInput() let nextButton = CustomButton() Layout: https://github.com/SnapKit/SnapKit
  11. View class InputView: View { } override func initialize() {

    addSubview(titleLabel) addSubview(textField) } let titleLabel = UILabel(numberOfLines: 0) let textField = MaskedInput() let nextButton = CustomButton() Layout: https://github.com/SnapKit/SnapKit
  12. View class InputView: View { } override func initialize() {

    addSubview(titleLabel) addSubview(textField) } override func installConstraints() { titleLabel.snp.makeConstraints { $0.left.top.right.equalToSuperview().inset(Metrics.normal) } textField.snp.makeConstraints { $0.left.right.equalTo(titleLabel) $0.top.equalTo(titleLabel.snp.bottom).offset(Metrics.large) } nextButton.snp.makeConstraints { $0.left.right.bottom.equalToSuperview() } } let titleLabel = UILabel(numberOfLines: 0) let textField = MaskedInput() let nextButton = CustomButton() Layout: https://github.com/SnapKit/SnapKit
  13. View class InputView: View { } override func initialize() {

    addSubview(titleLabel) addSubview(textField) } override func installConstraints() { titleLabel.snp.makeConstraints { $0.left.top.right.equalToSuperview().inset(Metrics.normal) } textField.snp.makeConstraints { $0.left.right.equalTo(titleLabel) $0.top.equalTo(titleLabel.snp.bottom).offset(Metrics.large) } nextButton.snp.makeConstraints { $0.left.right.bottom.equalToSuperview() } } ViewController Controller Cria Ações View ViewModel Ações Atualiza let titleLabel = UILabel(numberOfLines: 0) let textField = MaskedInput() let nextButton = CustomButton()
  14. ViewController ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFViewController: NUSViewController<CPFView, CPFViewModel> { }
  15. ViewController class CPFViewController: NUSViewController<CPFView, CPFViewModel> { } ViewController Controller Cria

    Ações View ViewModel Ações Atualiza override func bind(_ viewModel: CPFViewModel) { view.titleLabel.attributedText = viewModel.title view.textField.defaultTextAttributes = viewModel.textFieldStyle view.textField.typingAttributes = viewModel.textFieldStyle view.nextButton.bind(viewModel.buttonViewModel) typedView.nextButton.isHidden = viewModel.buttonHidden typedView.textField.textColor = viewModel.textColor }
  16. ViewController class CPFViewController: NUSViewController<CPFView, CPFViewModel> { } ViewController Controller Cria

    Ações View ViewModel Ações Atualiza lazy var cpf: Driver<String> = { return self.view.textField.rx.text .asDriver() .map { $0?.filterNonNumbers() } }() lazy var buttonAction: ControlEvent<Void> = { return self.view.nextButton.rx.tap }() override func bind(_ viewModel: CPFViewModel) { view.titleLabel.attributedText = viewModel.title view.textField.defaultTextAttributes = viewModel.textFieldStyle view.textField.typingAttributes = viewModel.textFieldStyle view.nextButton.bind(viewModel.buttonViewModel) typedView.nextButton.isHidden = viewModel.buttonHidden typedView.textField.textColor = viewModel.textColor }
  17. ViewController ViewController Controller Cria Ações View ViewModel Ações Atualiza override

    func bind(_ viewModel: CPFViewModel) { view.titleLabel.attributedText = viewModel.title view.textField.defaultTextAttributes = viewModel.textFieldStyle view.textField.typingAttributes = viewModel.textFieldStyle view.nextButton.bind(viewModel.buttonViewModel) typedView.nextButton.isHidden = viewModel.buttonHidden typedView.textField.textColor = viewModel.textColor } lazy var cpf: Driver<String> = { return self.view.textField.rx.text .asDriver() .map { $0?.filterNonNumbers() } }() lazy var buttonAction: ControlEvent<Void> = { return self.view.nextButton.rx.tap }() class CPFViewController: NUSViewController<CPFView, CPFViewModel> { }
  18. ViewModel struct CPFViewModel: ViewModelProtocol { let title: NSAttributedString let buttonViewModel:

    ButtonViewModel let buttonHidden: Bool let textColor: UIColor } ViewController Controller Cria Ações View ViewModel Ações Atualiza
  19. struct CPFViewModel: ViewModelProtocol { let title: NSAttributedString let buttonViewModel: ButtonViewModel

    let buttonHidden: Bool let textColor: UIColor } } ViewModel ViewController Controller Cria Ações View ViewModel Ações Atualiza extension CPFViewModel: InputViewModel, ShellLocalized { init(buttonHidden: Bool, textColor: UIColor) { title = LocalizedString(“LOGIN_CPF_TITLE”) .styled(with: type(of: self).titleStyle) buttonViewModel = ButtonViewModel(image:…) self.buttonHidden = buttonHidden self.textColor = textColor } AttributedString: https://github.com/Raizlabs/BonMot
  20. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController()
  21. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) }
  22. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } }
  23. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let buttonHidden = isValidCPF .startWith(false) .map { !$0 }
  24. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } let buttonHidden = isValidCPF .startWith(false) .map { !$0 }
  25. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } Driver.combineLatest(buttonHidden, textColor) .map { isHidden, textColor in return CPFViewModel(buttonHidden: isHidden, textColor: textColor) }.drive(onNext: viewController.bind) .disposed(by: disposeBag) let buttonHidden = isValidCPF .startWith(false) .map { !$0 }
  26. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } Driver.combineLatest(buttonHidden, textColor) .map { isHidden, textColor in return CPFViewModel(buttonHidden: isHidden, textColor: textColor) } let buttonHidden = isValidCPF .startWith(false) .map { !$0 } .drive(onNext: viewController.bind) .disposed(by: disposeBag)
  27. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } Driver.combineLatest(buttonHidden, textColor) .map { isHidden, textColor in return CPFViewModel(buttonHidden: isHidden, textColor: textColor) } let buttonHidden = isValidCPF .startWith(false) .map { !$0 } .drive(onNext: viewController.bind) .disposed(by: disposeBag)
  28. Controller ViewController Controller Cria Ações View ViewModel Ações Atualiza class

    CPFController: ControllerProtocol { } let viewController = CPFViewController() var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } init() { } var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } Driver.combineLatest(buttonHidden, textColor) .map { isHidden, textColor in return CPFViewModel(buttonHidden: isHidden, textColor: textColor) } let buttonHidden = isValidCPF .startWith(false) .map { !$0 } .drive(onNext: viewController.bind) .disposed(by: disposeBag)
  29. Controller class CPFController: ControllerProtocol { } let viewController = CPFViewController()

    var cpf: Driver<String> { return viewController.buttonAction.asDriver() .withLatestFrom(viewController.cpf) } ViewController Controller Cria Ações View ViewModel Ações Atualiza var isValidCPF: Driver<Bool> { return viewController.cpf .skipWhile { $0.count < 11 } .map { $0.isValidCPF } } init() { } let textColor = isValidCPF .map { isValid -> UIColor in isValid ? .nero : .mandy } let buttonHidden = isValidCPF .startWith(false) .map { !$0 } Driver.combineLatest(buttonHidden, textColor) .map { isHidden, textColor in return CPFViewModel(buttonHidden: isHidden, textColor: textColor) }.drive(onNext: viewController.bind) .disposed(by: disposeBag)