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

Max Alexander: MVVM with RxSwift

1fa9cb8c7997c8c4d3d251fb5e41f749?s=47 Realm
December 15, 2016

Max Alexander: MVVM with RxSwift

Abstract/excerpt: MVVM is the critical design pattern for front-end engineers. There are so many ways that objects can talk to each other in an iOS App: delegates, callbacks, notification. Max will show you how to streamline your development process in 3 easy patterns with RxSwift. He’ll go over the MVVM basics, creating custom observers, wrangling disparate APIs, and manipulating calls using concurrency and dispatch_queues. Your code will be so neat that you could quit your job and leave it for the next guy in impeccable condition.

Bio: Max Alexander has been a contract iOS Developer for the last 2 years and recently has been the iOS Engineer for Hipmunk. He deeply loves reactive apps and always loves to evangelize teams to use RxJS, RxSwift, and RxJava whenever possible. He has a particular interest in Operational Transform and Offline-First application development On his leisure time Max contributes to Github Projects, develops games on Unreal Engine 4 and Unity.

Twitter: https://twitter.com/maxofeden

1fa9cb8c7997c8c4d3d251fb5e41f749?s=128

Realm

December 15, 2016
Tweet

Transcript

  1. MVVM with RxSwift Maximilian Alexander

  2. https:/ /goo.gl/stQQDO View This Live with Me!

  3. What Does It Solve? MVC Pattern

  4. Massive View Controller

  5. Instead of Shoving EVERYTHING into your ViewController

  6. Model View ViewModel

  7. View

  8. UIButton UITextField UITableView UICollectionView NSLayoutConstraints View

  9. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton } Code So Far
  10. Model

  11. CLLocationManager Alamofire Facebook SDK Database like Realm Service Classes Model

  12. The Model is just an abstract word for "your data"

  13. ViewModel

  14. 1. Prepares Data 2. Manipulates Data

  15. struct LoginViewModel { var username: String = "" var password:

    String = "" func attemptToLogin() { let params = [ "username": username, "password": password ] ApiClient.shared.login(email: email, password: password) { (response, error) in } } }
  16. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton //viewModel is just a member variable here. var viewModel = LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() } }
  17. •UITextField •UITextField •UIButton ViewModel Model •tapFunction() •doSomething()

  18. Hooking UI to ViewModel and Back Not as simple as

    you think
  19. class LoginViewController : UIViewController { @IBOutlet weak var usernameTextField: UITextField!

    @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var confirmButton: UIButton //viewModel is just a member variable here. var viewModel = LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() usernameTextField.addTarget(self, action: #selector(LoginViewController.usernameTextFieldDidChange:_), forControlEvents: UIControlEvents.EditingChanged) passwordTextField.addTarget(self, action: #selector(LoginViewController.passworldTextFieldDidChange:_), forControlEvents: UIControlEvents.EditingChanged) } func usernameTextFieldDidChange(textField: UITextField){ viewModel.username = textField.text ?? "" } func passworldTextFieldDidChange(textField: UITextField){ viewModel.password = textField.text ?? "" } func confirmButtonTapped(sender: UIButton){ viewModel.attemptLogin() } }
  20. That looked less fun

  21. What about UI that reacts to changing events?

  22. Enabled/Disable Confirm Button aka. Form Validation

  23. •UITextField •UITextField •UIButton ViewModel Model •tapFunction() •doSomething() This is Hard

  24. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" func attemptToLogin() { let params = [ "username": username, "password": password ] ApiClient.shared.login(email: email, password: password) { (response, error) in } } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } }
  25. Now how do we hookup isValid to confirmButton?

  26. Important! ViewModel does not hold a reference to the ViewController!

  27. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" { didSet { loginViewController?.confirmButton.isDisabled = !isValid } } weak var loginViewController : LoginViewController? func attemptToLogin() { //truncated for space } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } } Never do this!
  28. View Model DONTS Reference View Controller Don't import UIKit Reference

    anything from UIKit Data should be going in and out
  29. struct LoginViewModel { var username: String = "" { didSet

    { evaluateValidity() } } var password: String = "" { didSet { evaluateValidity() } } var isValid : Bool = "" { didSet { isValidCallback?(isValid: isValid) } } var isValidCallback : ((isValid: Bool) -> Void)? func attemptToLogin() { //truncated for space } private func evaluateValidity(){ isValid = username.characters.count > 0 && password.characters.count > 0 } }
  30. class LoginViewController { @IBOutlet var confirmButton: UIButton! var loginViewModel =

    LoginViewModel() override func viewDidLoad(){ super.viewDidLoad() loginViewModel.isValidCallback = { [weak self] (isValid) in self?.confirmButton.isEnabled = isValid } } } Listen to the Callback
  31. That's it for MVVM!

  32. Unidirectional Data Flow with MVVM is hard

  33. What we want is something like this: UIKit ViewModel Model

  34. How can RxSwift help with this?

  35. import RxSwift struct LoginViewModel { var username = Variable<String>("") var

    password = Variable<String>("") var isValid : Observable<Bool>{ return Observable.combineLatest( self.username, self.password) { (username, password) in return username.characters.count > 0 && password.characters.count > 0 } } }
  36. import RxSwift import RxCocoa class LoginViewController { var usernameTextField: UITextField!

    var passwordTextField: UITextField! var confirmButton: UIBUtton! var viewModel = LoginViewModel() var disposeBag = DisposeBag() override func viewDidLoad(){ super.viewDidLoad() usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag) passwordTextField.rx.text.bindTo(viewModel.password).addTo(disposeBag) //from the viewModel viewModel.rx.isValid.map{ $0 } .bindTo(confirmButton.rx.isEnabled) } }
  37. That's How Easy It Is!

  38. Make sure the UIBindings Don't Talk to Each Other

  39. class ViewController : UIViewController { override func viewDidLoad(){ super.viewDidLoad() let

    isValid Observable.combineLatest( username.rx.text, password.rx.text, resultSelector: { (username, password) -> Bool in return username.characters.count > 0 && password.characters.count > 0 }) isValid.bindTo(confirmButton.rx.isEnabled) .addTo(disposeBag) } } ViewController is still massive
  40. class MyCustomView : UIView { var sink : AnyObserver<SomeComplexStructure> {

    return AnyObserver { [weak self] event in switch event { case .next(let data): self?.something.text = data.property.text break case .error(let error): self?.backgroundColor = .red case .completed: self.alpha = 0 } } } } What if RxSwift + RxCocoa doesn't have bindings
  41. class ViewController { let myCustomView : MyCustomView override func viewDidLoad(){

    super.viewDidLoad() viewModel.dataStream .bindTo(myCustomView.sink) .addTo(disposeBag) } } Custom Binding Sinks
  42. Looking Forward pod 'RxSwift' pod 'RxCocoa' #the good stuff pod

    'RxDataSource'