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

rx in the real world
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

Rx in the real world

1 Rob Ciolli

slide-2
SLIDE 2

2 Rob Ciolli

slide-3
SLIDE 3

3 Rob Ciolli

slide-4
SLIDE 4

The App

4 Rob Ciolli

slide-5
SLIDE 5

Quick architecture

  • verview

5 Rob Ciolli

slide-6
SLIDE 6

MV - WTF

6 Rob Ciolli

slide-7
SLIDE 7

Model

Simple, immutable data struct returned from DB or APIs

7 Rob Ciolli

slide-8
SLIDE 8

View

UI layout as defined in storyboard file

8 Rob Ciolli

slide-9
SLIDE 9

Controller

Regular Cocoa ViewController

protocol ViewControllerProtocol: class { ... associatedtype ViewModelType func recieve(viewModel: ViewModelType) }

9 Rob Ciolli

slide-10
SLIDE 10

Presenter

import UIKit protocol PresenterProtocol { associatedtype ViewControllerType: UIViewController func makeViewController() -> ViewControllerType }

10 Rob Ciolli

slide-11
SLIDE 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

slide-12
SLIDE 12

Example 1

Beer Detail Page

12 Rob Ciolli

slide-13
SLIDE 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

slide-14
SLIDE 14

Observable

  • next
  • complete
  • error

14 Rob Ciolli

slide-15
SLIDE 15

Observer

  • 'Listens' to an Observable
  • implements onNext, onCompleted, onError
  • subscribing or binding returns a Disposable
  • DisposeBag pattern

15 Rob Ciolli

slide-16
SLIDE 16

Observable example

let observable = Observable<String>.create { observer in

  • .onNext("beer")
  • .onNext("is")
  • .onNext("good")
  • .onCompleted()

return Disposables.create() }

16 Rob Ciolli

slide-17
SLIDE 17

Observer example

let disposeBag = DisposeBag()

  • bservable

.subscribe(

  • nNext: { s in print(s) },
  • nError: { _ in print("wtf") },
  • nCompleted: { _ in print("done") }

) .disposed(by: disposeBag)

17 Rob Ciolli

slide-18
SLIDE 18

Mutating the streams

Observable Operators

  • Transform => Map & FlatMap ...
  • Filter => Filter / Debounce / Skip / Take
  • Combine => Zip / CombineLatest
  • Error Handling => Retry / Catch

18 Rob Ciolli

slide-19
SLIDE 19

Data Layer

import RxSwift protocol Datalayer { func requestAllBeers() -> Observable<[Beer]> }

19 Rob Ciolli

slide-20
SLIDE 20

DetailPresenter

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

20 Rob Ciolli

slide-21
SLIDE 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

slide-22
SLIDE 22

Map

22 Rob Ciolli

slide-23
SLIDE 23

DetailViewController

class DetailViewController: UIViewController, ViewControllerProtocol { ... let disposeBag = DisposeBag() @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var imageView: UIImageView!

  • verride 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

slide-24
SLIDE 24

Show me the code

Example 1 - Beer Detail Page

24 Rob Ciolli

slide-25
SLIDE 25

! "

Binding is too verbose Only displays one piece of data Not really reactive

25 Rob Ciolli

slide-26
SLIDE 26

Example 1b

Beer Detail Page (again...)

heaps more reactive

26 Rob Ciolli

slide-27
SLIDE 27

Binding

27 Rob Ciolli

slide-28
SLIDE 28

what if....

viewModel.name.asObservable() .bindTo(nameLabel.rx.text)

looked like

nameLabel.rx.text <- viewModel.name

28 Rob Ciolli

slide-29
SLIDE 29

... and you could add all disposables to disposeBag in

  • ne call

disposeBag.dispose([ nameLabel.rx.text <- viewModel.name, styleLabel.rx.text <- viewModel.style, brewryLabel.rx.text <- viewModel.brewry, ... ])

29 Rob Ciolli

slide-30
SLIDE 30

<- operator

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

30 Rob Ciolli

slide-31
SLIDE 31

DisposeBag

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

31 Rob Ciolli

slide-32
SLIDE 32

ViewModel Requirements

Page through [Beer]

protocol DetailViewModelDelegateProtocol: ViewModelDelegateProtocol { var beers: Observable<[Beer]> { get } ... }

32 Rob Ciolli

slide-33
SLIDE 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

slide-34
SLIDE 34

Combine Latest

34 Rob Ciolli

slide-35
SLIDE 35

ViewModel Requirements

Be heaps more reactive

Observable.combineLatest(delegate.beers, index.asObservable(), resultSelector: selectBeer)

35 Rob Ciolli

slide-36
SLIDE 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

slide-37
SLIDE 37

Show me the code (... and tests)

Example 1b - Beer Detail Page

37 Rob Ciolli

slide-38
SLIDE 38

Example 2

Sign in Page

capture user input validate input call api and handle response

38 Rob Ciolli

slide-39
SLIDE 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

slide-40
SLIDE 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

slide-41
SLIDE 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

slide-42
SLIDE 42

Show me the code

Example 2 - Beer Sign in

42 Rob Ciolli

slide-43
SLIDE 43

Example 3

Beer ListView

filter list show detail view

43 Rob Ciolli

slide-44
SLIDE 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

slide-45
SLIDE 45

Filter

45 Rob Ciolli

slide-46
SLIDE 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

slide-47
SLIDE 47

List ViewModel

private func onItemTapped(index: Int) { delegate.showDetail(beers: filteredBeers(), index: index) }

47 Rob Ciolli

slide-48
SLIDE 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

slide-49
SLIDE 49

Show me the code

Example 3 - Beer List

49 Rob Ciolli

slide-50
SLIDE 50

Wash up

  • single responsibility is good
  • Rx makes us think about data flow
  • infix operators and swift extensions are cool

50 Rob Ciolli

slide-51
SLIDE 51

Resources

  • http:/

/reactivex.io/

  • http:/

/rxmarbles.com/

  • https:/

/github.com/ReactiveX/RxSwift

51 Rob Ciolli

slide-52
SLIDE 52

Thank you

@boblaroc <- ( , )

52 Rob Ciolli