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

MVI - a reactive architecture for iOS

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

MVI - a reactive architecture for iOS

Presented on August'18 at Swifters-Bangalore Meetup.
https://www.meetup.com/Swifters-Bangalore/events/253776466/

Avatar for Rugmangathan

Rugmangathan

August 25, 2018

Other Decks in Programming

Transcript

  1. Intention • Intentions take elementary event streams and translate them

    into something that model can understand.
 • Intentions arise from view and are inputs to the model.
  2. • Two elementary events • Increment clicks • Decrement clicks

    • A lifecycle stream. • A states stream.
  3. CounterIntentions class CounterIntentions { private let incrementClicks: Observable<Void> private let

    decrementClicks: Observable<Void> init(_ incrementClicks: Observable<Void>, _ decrementClicks: Observable<Void>) { self.incrementClicks = incrementClicks self.decrementClicks = decrementClicks } func increment() !-> Observable<Int64> { return incrementClicks.map { 1 } } func decrement() !-> Observable<Int64> { return decrementClicks.map { -1 } } }
  4. CounterIntentions class CounterIntentions { private let incrementClicks: Observable<Void> private let

    decrementClicks: Observable<Void> init(_ incrementClicks: Observable<Void>, _ decrementClicks: Observable<Void>) { self.incrementClicks = incrementClicks self.decrementClicks = decrementClicks } func increment() !-> Observable<Int64> { return incrementClicks.map { 1 } } func decrement() !-> Observable<Int64> { return decrementClicks.map { -1 } } }
  5. CounterModel class CounterModel { static func bind(_ intentions: CounterIntentions, _

    lifecycle: Observable<MviLifecycle>,
 _ states: Observable<CounterState>) !-> Observable<CounterState> { 
 !// Business logic } }
  6. CounterModel 
 !// lifecycle event !-> created
 let createdLifecycleStates =

    lifecycle
 .filter { $0 !== .created }
 .flatMapLatest { _ !-> Observable<CounterState> in
 return Observable.just(initialState) } return Observable .merge(createdLifecycleStates)

  7. CounterModel 
 !// lifecycle event !-> restored
 let restoredLifecycleStates =

    lifecycle
 .filter { $0 !== .restored }
 .withLatestFrom(states) { (_, state: CounterState) !-> CounterState in
 return state } return Observable .merge(createdLifecycleStates, restoredLifecycleStates)
  8. CounterModel !// incremented states
 let incrementedStates = intentions .increment()
 .withLatestFrom(states)

    { (value: Int64, state: CounterState) !-> CounterState in
 let copiedState = CounterState(state)
 copiedState.clicks += 1 copiedState.count += value
 return state } return Observable .merge(createdLifecycleStates, restoredLifecycleStates, incrementedStates)
  9. CounterModel !// decremented states
 let decrementedStates = intentions .decrement()
 .withLatestFrom(states)

    { (value: Int64, state: CounterState) !-> CounterState in
 let copiedState = CounterState(state)
 copiedState.clicks += 1 copiedState.count += value
 return state } return Observable .merge(createdLifecycleStates, restoredLifecycleStates, incrementedStates, decrementedStates)
  10. CounterView protocol CounterView { func showCounterValues(_ count: Int64, _ clicks:

    Int64) } class CounterViewRenderer { let view: CounterView init(_ view: CounterView) { self.view = view } func render(_ state: CounterState) { view.showCounterValues(state.count, state.clicks) } }
  11. CounterViewController class CounterViewController: MviController<CounterState> { @IBOutlet weak var clicksLabel: UILabel!

    @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var incrementButton: UIButton! @IBOutlet weak var decrementButton: UIButton! private lazy var intentions: CounterIntentions = { return CounterIntentions(incrementButton.rx.tap.asObservable(), decrementButton.rx.tap.asObservable()) }() private lazy var renderer = { return CounterViewRenderer(self) }() override func bind(states: Observable<CounterState>) !-> Observable<CounterState> { return CounterModel .bind(intentions, lifecycle.asObservable(), states) } override func effects(state: CounterState) { renderer.render(state) } } extension CounterViewController: CounterView { func showCounterValues(_ count: Int64, _ clicks: Int64) { countLabel.text = "\(count)" clicksLabel.text = "\(clicks)" } }
  12. CounterViewController class CounterViewController: MviController<CounterState> { @IBOutlet weak var clicksLabel: UILabel!

    @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var incrementButton: UIButton! @IBOutlet weak var decrementButton: UIButton! private lazy var intentions: CounterIntentions = { return CounterIntentions(incrementButton.rx.tap.asObservable(), decrementButton.rx.tap.asObservable()) }() private lazy var renderer = { return CounterViewRenderer(self) }() override func bind(states: Observable<CounterState>) !-> Observable<CounterState> { return CounterModel .bind(intentions, lifecycle.asObservable(), states) } override func effects(state: CounterState) { renderer.render(state) } } extension CounterViewController: CounterView { func showCounterValues(_ count: Int64, _ clicks: Int64) { countLabel.text = "\(count)" clicksLabel.text = "\(clicks)" } }
  13. CounterViewController class CounterViewController: MviController<CounterState> { @IBOutlet weak var clicksLabel: UILabel!

    @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var incrementButton: UIButton! @IBOutlet weak var decrementButton: UIButton! private lazy var intentions: CounterIntentions = { return CounterIntentions(incrementButton.rx.tap.asObservable(), decrementButton.rx.tap.asObservable()) }() private lazy var renderer = { return CounterViewRenderer(self) }() override func bind(states: Observable<CounterState>) !-> Observable<CounterState> { return CounterModel .bind(intentions, lifecycle.asObservable(), states) } override func effects(state: CounterState) { renderer.render(state) } } extension CounterViewController: CounterView { func showCounterValues(_ count: Int64, _ clicks: Int64) { countLabel.text = "\(count)" clicksLabel.text = "\(clicks)" } }
  14. CounterViewController class CounterViewController: MviController<CounterState> { @IBOutlet weak var clicksLabel: UILabel!

    @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var incrementButton: UIButton! @IBOutlet weak var decrementButton: UIButton! private lazy var intentions: CounterIntentions = { return CounterIntentions(incrementButton.rx.tap.asObservable(), decrementButton.rx.tap.asObservable()) }() private lazy var renderer = { return CounterViewRenderer(self) }() override func bind(states: Observable<CounterState>) !-> Observable<CounterState> { return CounterModel .bind(intentions, lifecycle.asObservable(), states) } override func effects(state: CounterState) { renderer.render(state) } } extension CounterViewController: CounterView { func showCounterValues(_ count: Int64, _ clicks: Int64) { countLabel.text = "\(count)" clicksLabel.text = "\(clicks)" } }
  15. CounterViewController class CounterViewController: MviController<CounterState> { @IBOutlet weak var clicksLabel: UILabel!

    @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var incrementButton: UIButton! @IBOutlet weak var decrementButton: UIButton! private lazy var intentions: CounterIntentions = { return CounterIntentions(incrementButton.rx.tap.asObservable(), decrementButton.rx.tap.asObservable()) }() private lazy var renderer = { return CounterViewRenderer(self) }() override func bind(states: Observable<CounterState>) !-> Observable<CounterState> { return CounterModel .bind(intentions, lifecycle.asObservable(), states) } override func effects(state: CounterState) { renderer.render(state) } } extension CounterViewController: CounterView { func showCounterValues(_ count: Int64, _ clicks: Int64) { countLabel.text = "\(count)" clicksLabel.text = "\(clicks)" } }
  16. References • https:!//!!www.youtube.com/watch?v=1zj7M1LnJV4 (Andre Staltz’s presentation on MVI) • https:!//gist.github.com/staltz/868e7e9bc2a7b8c1f754

    (Introduction to Reactive Programming) • https:!//medium.com/@ragunathjawahar/mvi-series-a- pragmatic-reactive-architecture-for-android