rx in the real world
play

Rx in the real world 1 Rob Ciolli 2 Rob Ciolli 3 Rob Ciolli - PowerPoint PPT Presentation

Rx in the real world 1 Rob Ciolli 2 Rob Ciolli 3 Rob Ciolli The App 4 Rob Ciolli Quick architecture overview 5 Rob Ciolli MV - WTF 6 Rob Ciolli Model Simple, immutable data struct returned from DB or APIs 7 Rob Ciolli View UI


  1. Rx in the real world 1 Rob Ciolli

  2. 2 Rob Ciolli

  3. 3 Rob Ciolli

  4. The App 4 Rob Ciolli

  5. Quick architecture overview 5 Rob Ciolli

  6. MV - WTF 6 Rob Ciolli

  7. Model Simple, immutable data struct returned from DB or APIs 7 Rob Ciolli

  8. View UI layout as defined in storyboard file 8 Rob Ciolli

  9. Controller Regular Cocoa ViewController protocol ViewControllerProtocol: class { ... associatedtype ViewModelType func recieve(viewModel: ViewModelType) } 9 Rob Ciolli

  10. Presenter import UIKit protocol PresenterProtocol { associatedtype ViewControllerType: UIViewController func makeViewController() -> ViewControllerType } 10 Rob Ciolli

  11. ViewModel protocol ViewModelDelegateProtocol { } protocol ViewModelProtocol { associatedtype DelegateType //: ViewModelDelegateProtocol var delegate: DelegateType { get } init(delegate: DelegateType) } class BaseViewModel<T> : ViewModelProtocol { let delegate: T required init(delegate: T) { self.delegate = delegate } } 11 Rob Ciolli

  12. Example 1 Beer Detail Page 12 Rob Ciolli

  13. Beer Model struct Beer { let id: Int let name: String let style: String let brewry: String let abv: Double let ibu: Double let description: String let image: String } 13 Rob Ciolli

  14. Observable • next • complete • error 14 Rob Ciolli

  15. Observer • 'Listens' to an Observable • implements onNext, onCompleted, onError • subscribing or binding returns a Disposable • DisposeBag pattern 15 Rob Ciolli

  16. Observable example let observable = Observable<String>.create { observer in o.onNext("beer") o.onNext("is") o.onNext("good") o.onCompleted() return Disposables.create() } 16 Rob Ciolli

  17. Observer example let disposeBag = DisposeBag() observable .subscribe( onNext: { s in print(s) }, onError: { _ in print("wtf") }, onCompleted: { _ in print("done") } ) .disposed(by: disposeBag) 17 Rob Ciolli

  18. Mutating the streams Observable Operators • Transform => Map & FlatMap ... • Filter => Filter / Debounce / Skip / Take • Combine => Zip / CombineLatest • Error Handling => Retry / Catch 18 Rob Ciolli

  19. Data Layer import RxSwift protocol Datalayer { func requestAllBeers() -> Observable<[Beer]> } 19 Rob Ciolli

  20. DetailPresenter struct DetailPresenter { let beer: Beer init(beer: Beer) { self.beer = beer } } extension DetailPresenter: PresenterProtocol { ... } extension DetailPresenter: DetailViewModelDelegateProtocol { } 20 Rob Ciolli

  21. DetailViewModel protocol DetailViewModelDelegateProtocol: ViewModelDelegateProtocol { var beer: Beer { get } } class DetailViewModel: BaseViewModel<DetailViewModelDelegateProtocol> { let name = Variable<String?>("") ... required init(delegate: DetailViewModelDelegateProtocol) { super.init(delegate: delegate) name.value = beer.name ... } } 21 Rob Ciolli

  22. Map 22 Rob Ciolli

  23. DetailViewController class DetailViewController: UIViewController, ViewControllerProtocol { ... let disposeBag = DisposeBag() @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() viewModel.name.asObservable() .bindTo(nameLabel.rx.text) .disposed(by: disposeBag) viewModel.image.asObservable() .map { UIImage(named: $0)! } .bindTo(imageView.rx.image) .disposed(by: disposeBag) } } 23 Rob Ciolli

  24. Show me the code Example 1 - Beer Detail Page 24 Rob Ciolli

  25. ! " Binding is too verbose Only displays one piece of data Not really reactive 25 Rob Ciolli

  26. Example 1b Beer Detail Page ( again... ) heaps more reactive 26 Rob Ciolli

  27. Binding 27 Rob Ciolli

  28. what if.... viewModel.name.asObservable() .bindTo(nameLabel.rx.text) looked like nameLabel.rx.text <- viewModel.name 28 Rob Ciolli

  29. ... and you could add all disposables to disposeBag in one call disposeBag.dispose([ nameLabel.rx.text <- viewModel.name, styleLabel.rx.text <- viewModel.style, brewryLabel.rx.text <- viewModel.brewry, ... ]) 29 Rob Ciolli

  30. <- operator infix operator <- func <- <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable { return variable.asObservable().bind(to: property) } 30 Rob Ciolli

  31. DisposeBag extension DisposeBag { func dispose(_ disposables: [Disposable]) { disposables.forEach { [unowned self] disposable in self.insert(disposable) } } } 31 Rob Ciolli

  32. ViewModel Requirements Page through [ Beer ] protocol DetailViewModelDelegateProtocol: ViewModelDelegateProtocol { var beers: Observable<[Beer]> { get } ... } 32 Rob Ciolli

  33. ViewModel Requirements React to Next/Prev let next = Variable<()>() let index: Variable<Int> ... next.asObservable().subscribe(onNext: onNext) ... private func onNext() { index.value += 1 } 33 Rob Ciolli

  34. Combine Latest 34 Rob Ciolli

  35. ViewModel Requirements Be heaps more reactive Observable.combineLatest(delegate.beers, index.asObservable(), resultSelector: selectBeer) 35 Rob Ciolli

  36. ViewModel Requirements Control enabled state of UIButton extension UIButton { var rx_driveEnable: AnyObserver<Bool> { return UIBindingObserver(UIElement: self) { button, enabled in button.isUserInteractionEnabled = enabled }.asObserver() } } 36 Rob Ciolli

  37. Show me the code (... and tests) Example 1b - Beer Detail Page 37 Rob Ciolli

  38. Example 2 Sign in Page capture user input validate input call api and handle response 38 Rob Ciolli

  39. Sign in ViewModel initialise protocol SigninViewModelDelegateProtocol: ViewModelDelegateProtocol { func signin(username: String, password: String) -> Observable<Void> } class SigninViewModel: BaseViewModel<SigninViewModelDelegateProtocol> { ... let username = Variable<String?>(nil) let password = Variable<String?>(nil) let signInTapped = Variable<()>() let error = Variable<String?>(nil) let signedIn = Variable<()>() 39 Rob Ciolli

  40. Sign in ViewModel call api ... signInTapped.asObservable() .subscribe(onNext: signin) .disposed(by: disposeBag) ... private func signin() { guard let username = username.value, let password = password.value else { return } delegate.signin(username: username, password: password) .subscribe(onNext: onSignedIn, onError: onSignInError) .disposed(by: disposeBag) } private func onSignedIn() { signedIn.value = () } private func onSignInError(_: Error) { error.value = "Error signing in" } 40 Rob Ciolli

  41. Sign in ViewModel input validation var signInEnabled: Observable<Bool> { return Observable.combineLatest(username.asObservable(), password.asObservable(), resultSelector: inputIsValid) } private func inputIsValid(username: String?, password:String?) -> Bool { guard let username = username, let password = password else { return false } return !username.isEmpty && !password.isEmpty } } 41 Rob Ciolli

  42. Show me the code Example 2 - Beer Sign in 42 Rob Ciolli

  43. Example 3 Beer ListView filter list show detail view 43 Rob Ciolli

  44. List ViewModel let searchVariable = Variable<String?>("") let itemTapped = Variable<Int>(-1) required init(delegate: ListViewModelDelegateProtocol) { ... itemTapped.asObservable() .skip(1) .subscribe(onNext: onItemTapped) .disposed(by: disposeBag) } 44 Rob Ciolli

  45. Filter 45 Rob Ciolli

  46. List ViewModel func filteredBeers() -> Observable<[Beer]> { let search = searchVariable.asObservable() return Observable.combineLatest(delegate.beers, search, resultSelector: filterBeers) } private func filterBeers(beers: [Beer], search: String?) -> [Beer] { guard let search = search else { return beers } return beers.filter { search.isEmpty || $0.name.lowercased().contains(search.lowercased()) } } 46 Rob Ciolli

  47. List ViewModel private func onItemTapped(index: Int) { delegate.showDetail(beers: filteredBeers(), index: index) } 47 Rob Ciolli

  48. List ViewController viewModel.filteredBeers() .debounce(0.3, scheduler: MainScheduler.instance) .bind(to: tableView.rx.items( cellIdentifier: "Cell", cellType: UITableViewCell.self), curriedArgument: initialiseCell) 48 Rob Ciolli

  49. Show me the code Example 3 - Beer List 49 Rob Ciolli

  50. Wash up • single responsibility is good • Rx makes us think about data flow • infix operators and swift extensions are cool 50 Rob Ciolli

  51. Resources • http:/ /reactivex.io/ • http:/ /rxmarbles.com/ • https:/ /github.com/ReactiveX/RxSwift 51 Rob Ciolli

  52. Thank you @boblaroc <- ( , ) 52 Rob Ciolli

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend