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

MVI - Reactive Architecture for Modern Applicat...

Dinesh Raja
September 01, 2018

MVI - Reactive Architecture for Modern Applications

Presented on Swift Delhi Meetup - Chapter 13.
Date: Sept 1, 2018 at UrbanClap.

https://www.meetup.com/Swift-Delhi/events/254037961/

Slides with Audio: https://youtu.be/kYtE7i49DXA
Mvi-Example Repo: https://github.com/dinarajas/BmiCalc

Dinesh Raja

September 01, 2018
Tweet

Other Decks in Programming

Transcript

  1. class BmiIntentions { private let heightChanges: Observable<SliderPayload> private let weightChanges:

    Observable<SliderPayload> init( _ heightChanges: Observable<SliderPayload>, _ weightChanges: Observable<SliderPayload> ) { self.heightChanges = heightChanges self.weightChanges = weightChanges } func height() -> Observable<Int> { return heightChanges .map { payload in payload.calculatedValue() } } func weight() -> Observable<Int> { return weightChanges .map { payload in payload.calculatedValue() } } }
  2. class BmiIntentions { private let heightChanges: Observable<SliderPayload> private let weightChanges:

    Observable<SliderPayload> init( _ heightChanges: Observable<SliderPayload>, _ weightChanges: Observable<SliderPayload> ) { self.heightChanges = heightChanges self.weightChanges = weightChanges } func height() -> Observable<Int> { return heightChanges .map { payload in payload.calculatedValue() } } func weight() -> Observable<Int> { return weightChanges .map { payload in payload.calculatedValue() } } }
  3. class BmiIntentions { private let heightChanges: Observable<SliderPayload> private let weightChanges:

    Observable<SliderPayload> init( _ heightChanges: Observable<SliderPayload>, _ weightChanges: Observable<SliderPayload> ) { self.heightChanges = heightChanges self.weightChanges = weightChanges } func height() -> Observable<Int> { return heightChanges .map { payload in payload.calculatedValue() } } func weight() -> Observable<Int> { return weightChanges .map { payload in payload.calculatedValue() } } }
  4. class BmiIntentions { private let heightChanges: Observable<SliderPayload> private let weightChanges:

    Observable<SliderPayload> init( _ heightChanges: Observable<SliderPayload>, _ weightChanges: Observable<SliderPayload> ) { self.heightChanges = heightChanges self.weightChanges = weightChanges } func height() -> Observable<Int> { return heightChanges .map { payload in payload.calculatedValue() } } func weight() -> Observable<Int> { return weightChanges .map { payload in payload.calculatedValue() } } }
  5. class BmiIntentions { private let heightChanges: Observable<SliderPayload> private let weightChanges:

    Observable<SliderPayload> init( _ heightChanges: Observable<SliderPayload>, _ weightChanges: Observable<SliderPayload> ) { self.heightChanges = heightChanges self.weightChanges = weightChanges } func height() -> Observable<Int> { return heightChanges .map { payload in payload.calculatedValue() } } func weight() -> Observable<Int> { return weightChanges .map { payload in payload.calculatedValue() } } }
  6. BmiModel class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>,

    _ states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } }
  7. class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>, _

    states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } } BmiModel
  8. class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>, _

    states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } } BmiModel
  9. class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>, _

    states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } } BmiModel
  10. class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>, _

    states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } } BmiModel
  11. BmiModel class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>,

    _ states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } }
  12. BmiModel class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>,

    _ states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } }
  13. BmiModel class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>,

    _ states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } }
  14. BmiModel class BmiModel { static func bind( _ lifecycle: Observable<MviLifecycle>,

    _ states: Observable<BmiState>, _ intentions: BmiIntentions ) -> Observable<BmiState> { let createdLifecycleStates = lifecycleCreatedUseCase(lifecycle) let restoredLifecycleStates = lifecycleRestoredUseCase(lifecycle, states) let heightChangeStates = heightChangesUseCase(intentions.height(), states) let weightChangeStates = weightChangesUseCase(intentions.weight(), states) return Observable.merge( createdLifecycleStates, restoredLifecycleStates, heightChangeStates, weightChangeStates ) } }
  15. Created - Use Case private static func lifecycleCreatedUseCase( _ lifecycle:

    Observable<MviLifecycle> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .created } .map { _ -> BmiState in let defaultHeight = 160 let defaultWeight = 40 let bmi = calculateBmi(height: defaultHeight, weight: defaultWeight) return BmiState.initial(height: defaultHeight, weight: defaultWeight, bmi: bmi) } }
  16. Created - Use Case private static func lifecycleCreatedUseCase( _ lifecycle:

    Observable<MviLifecycle> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .created } .map { _ -> BmiState in let defaultHeight = 160 let defaultWeight = 40 let bmi = calculateBmi(height: defaultHeight, weight: defaultWeight) return BmiState.initial(height: defaultHeight, weight: defaultWeight, bmi: bmi) } }
  17. Created - Use Case private static func lifecycleCreatedUseCase( _ lifecycle:

    Observable<MviLifecycle> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .created } .map { _ -> BmiState in let defaultHeight = 160 let defaultWeight = 40 let bmi = calculateBmi(height: defaultHeight, weight: defaultWeight) return BmiState.initial(height: defaultHeight, weight: defaultWeight, bmi: bmi) } }
  18. Created - Use Case private static func lifecycleCreatedUseCase( _ lifecycle:

    Observable<MviLifecycle> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .created } .map { _ -> BmiState in let defaultHeight = 160 let defaultWeight = 40 let bmi = calculateBmi(height: defaultHeight, weight: defaultWeight) return BmiState.initial(height: defaultHeight, weight: defaultWeight, bmi: bmi) } }
  19. Created - Use Case private static func lifecycleCreatedUseCase( _ lifecycle:

    Observable<MviLifecycle> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .created } .map { _ -> BmiState in let defaultHeight = 160 let defaultWeight = 40 let bmi = calculateBmi(height: defaultHeight, weight: defaultWeight) return BmiState.initial(height: defaultHeight, weight: defaultWeight, bmi: bmi) } }
  20. Restored - Use Case private static func lifecycleRestoredUseCase( _ lifecycle:

    Observable<MviLifecycle>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .restored } .withLatestFrom(states) .map { (state: BmiState) in state.restored() } }
  21. Restored - Use Case private static func lifecycleRestoredUseCase( _ lifecycle:

    Observable<MviLifecycle>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .restored } .withLatestFrom(states) .map { (state: BmiState) in state.restored() } }
  22. Restored - Use Case private static func lifecycleRestoredUseCase( _ lifecycle:

    Observable<MviLifecycle>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return lifecycle .filter { lifecycle in lifecycle == .restored } .withLatestFrom(states) .map { (state: BmiState) in state.restored() } }
  23. Height Changes - Use Case private static func heightChangesUseCase( _

    heightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return heightChanges .withLatestFrom(states) { (height: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: height, weight: state.weight) return state.heightChanged(height: height, bmi: bmi) } }
  24. Height Changes - Use Case private static func heightChangesUseCase( _

    heightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return heightChanges .withLatestFrom(states) { (height: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: height, weight: state.weight) return state.heightChanged(height: height, bmi: bmi) } }
  25. Height Changes - Use Case private static func heightChangesUseCase( _

    heightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return heightChanges .withLatestFrom(states) { (height: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: height, weight: state.weight) return state.heightChanged(height: height, bmi: bmi) } }
  26. Height Changes - Use Case private static func heightChangesUseCase( _

    heightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return heightChanges .withLatestFrom(states) { (height: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: height, weight: state.weight) return state.heightChanged(height: height, bmi: bmi) } }
  27. Weight Changes - Use Case private static func weightChangesUseCase( _

    weightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return weightChanges .withLatestFrom(states) { (weight: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: state.height, weight: weight) return state.weightChanged(weight: weight, bmi: bmi) } }
  28. Weight Changes - Use Case private static func weightChangesUseCase( _

    weightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return weightChanges .withLatestFrom(states) { (weight: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: state.height, weight: weight) return state.weightChanged(weight: weight, bmi: bmi) } }
  29. Weight Changes - Use Case private static func weightChangesUseCase( _

    weightChanges: Observable<Int>, _ states: Observable<BmiState> ) -> Observable<BmiState> { return weightChanges .withLatestFrom(states) { (weight: Int, state: BmiState) -> BmiState in let bmi = calculateBmi(height: state.height, weight: weight) return state.weightChanged(weight: weight, bmi: bmi) } }
  30. BmiState State Reducer struct BmiState: MviState { let height: Int

    let weight: Int let bmi: Double } extension BmiState { static func initial(height: Int, weight: Int, bmi: Double) -> BmiState { return BmiState(height: height, weight: weight, bmi: bmi) } func heightChanged(height: Int, bmi: Double) -> BmiState { return BmiState(height: height, weight: weight, bmi: bmi) } // Restore & Weight Change method goes here.. }
  31. protocol BmiView { func render(state: BmiState) func showBmi(bmi: Double, height:

    Int, weight: Int) } extension BmiView { func render(state: BmiState) { showBmi(bmi: state.bmi, height: state.height, weight: state.weight) } } BmiView
  32. class BmiViewController: MviController<BmiState> { @IBOutlet weak var heightSlider: UISlider! @IBOutlet

    weak var weightSlider: UISlider! private lazy var weightChanges = weightSlider.rx .value .map { progress in SliderPayload(min: self.minWeight, max: self.maxWeight, progress: progress) } private lazy var heightChanges = heightSlider.rx .value .map { progress in SliderPayload(min: self.minHeight, max: self.maxHeight, progress: progress) } private lazy var intentions = BmiIntentions(heightChanges, weightChanges) override func bind( states: Observable<BmiState>, lifecycle: Observable<MviLifecycle> ) -> Observable<BmiState> { return BmiModel.bind(lifecycle, states, intentions) } override func emitted(state: BmiState) { render(state: state) } }
  33. class BmiViewController: MviController<BmiState> { @IBOutlet weak var heightSlider: UISlider! @IBOutlet

    weak var weightSlider: UISlider! private lazy var weightChanges = weightSlider.rx .value .map { progress in SliderPayload(min: self.minWeight, max: self.maxWeight, progress: progress) } private lazy var heightChanges = heightSlider.rx .value .map { progress in SliderPayload(min: self.minHeight, max: self.maxHeight, progress: progress) } private lazy var intentions = BmiIntentions(heightChanges, weightChanges) override func bind( states: Observable<BmiState>, lifecycle: Observable<MviLifecycle> ) -> Observable<BmiState> { return BmiModel.bind(lifecycle, states, intentions) } override func emitted(state: BmiState) { render(state: state) } }
  34. class BmiViewController: MviController<BmiState> { @IBOutlet weak var heightSlider: UISlider! @IBOutlet

    weak var weightSlider: UISlider! private lazy var weightChanges = weightSlider.rx .value .map { progress in SliderPayload(min: self.minWeight, max: self.maxWeight, progress: progress) } private lazy var heightChanges = heightSlider.rx .value .map { progress in SliderPayload(min: self.minHeight, max: self.maxHeight, progress: progress) } private lazy var intentions = BmiIntentions(heightChanges, weightChanges) override func bind( states: Observable<BmiState>, lifecycle: Observable<MviLifecycle> ) -> Observable<BmiState> { return BmiModel.bind(lifecycle, states, intentions) } override func emitted(state: BmiState) { render(state: state) } }
  35. class BmiViewController: MviController<BmiState> { @IBOutlet weak var heightSlider: UISlider! @IBOutlet

    weak var weightSlider: UISlider! private lazy var weightChanges = weightSlider.rx .value .map { progress in SliderPayload(min: self.minWeight, max: self.maxWeight, progress: progress) } private lazy var heightChanges = heightSlider.rx .value .map { progress in SliderPayload(min: self.minHeight, max: self.maxHeight, progress: progress) } private lazy var intentions = BmiIntentions(heightChanges, weightChanges) override func bind( states: Observable<BmiState>, lifecycle: Observable<MviLifecycle> ) -> Observable<BmiState> { return BmiModel.bind(lifecycle, states, intentions) } override func emitted(state: BmiState) { render(state: state) } }
  36. BmiView (Implementation) extension BmiViewController: BmiView { func showBmi(bmi: Double, height:

    Int, weight: Int) { let bmiString = bmiNumberFormatter .string(from: NSNumber(value: bmi)) bmiValueLabel.text = bmiString heightLabel.text = "\(height)" weightLabel.text = "\(weight)" } }
  37. Freebies • Collaboration between teams • Less mocking in Testing

    • Peer-to-Peer code reviews • Fault tolerant system
  38. Freebies • Collaboration between teams • Less mocking in Testing

    • Peer-to-Peer code reviews • Fault tolerant system • Easy to debug