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

MVVM + RxSwift + DataControllers

Esteban Torres
September 16, 2016

MVVM + RxSwift + DataControllers

Talk given at NSSpain 2016 http://2016.nsspain.com/schedule/#session-215).

How we at Brewbot.io tackled the missing «Data management» on MVVM and glued everything together with RxSwift

Esteban Torres

September 16, 2016
Tweet

More Decks by Esteban Torres

Other Decks in Technology

Transcript

  1. Good morning everyone; I would like to take some moments
    to thank the organizers for such a nice conference so far
    and for the great talks that are still to come.
    Also want to thank you all for coming to listen to me talk
    about MVVM, RxSwift and how we use all of this at Brewbot.
    MVVM, RxSwift
    and DataControllers
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  2. Before diving head first into this «new» concept, let's go through
    what's the current state of afairs when it comes to architecturing an
    app (just some examples though)
    First let's add
    some context
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  3. Basically MVC is great for certain types of apps; but not
    so much for the flow that our apps have (most iOS apps).
    MVC
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  4. We all are well aware that MVC ends up being a gigantic pile of code
    embedded in the controller; which doesn't fully controls the model or
    the view but handles pretty much everything between heaven and hell
    And here comes the joke that should make an appearance in every
    presentation that references MCV; we end up with Massive View
    Controllers (this is your cue to laugh !).
    What's the «Problem»
    of MVC?
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  5. But fret no more my dear friends; Microsoft got us covered with
    MVVM (FYI I'm a HUGE believer of the benefits of MVVM) which help
    us separate even better the concerns of our app with a better
    defined architecture.
    We all know the old «View - ViewModel - Model» concept but if
    for some reason you don't we'll cover the basics here.
    MVVM
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  6. Basically we get the Model, which is the representation of our data, the purest object/
    struct in our codebase, it shouldn't be much more than a container/representation of the
    data that's coming to us from our datasource, wether that's a DB, a server, etc it doesn't
    matter.
    Model
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  7. We have our View which is the Graphical representation of said
    data, this means basically, how the user «consumes» the data
    from the models.
    View
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  8. And here comes the great savior, the ViewModel which is «nothing more» than a bridge
    between the model and the view, this way we can manipulate our «raw» data to a more
    presentable and better structure representation of the model so that the View can present
    it nice and pretty to the user without the need to actually compute or format anything.
    ViewModel
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  9. class MyViewModel {
    // The model being «formatted»
    private var model: MyModel? = nil
    init(model: MyModel? = nil) { … }
    // A formatted property
    var amountDueText: String {
    let currencyFormatter = NSNumberFormatter()
    currencyFormatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    currencyFormatter.locale = NSLocale.currentLocale()
    return currencyFormatter.stringFromNumber(model?.amount ?? 0) ?? "N/A"
    }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  10. OK, we now know our architectures (at least a couple) and
    we are ready to roll our app.
    Everything makes perfect sense here, you go to your
    Storyboards, XIBs or via code (I'm not going to start a
    #holy-war about it) and create the visual containers for the
    information that we will be presenting to the user.
    For this presentation's sake we will use MVVM and the data
    will come from an API on our server.
    What now?
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  11. Let's make
    some UI
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  12. We go and define our models (I hope you are using structs here
    !) based on a JSON response from the server or based on the DB
    schema.
    Add some Models
    struct Hop {
    let uuid: String
    let name: String?
    let amount: Double
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  13. We decided to go with MVVM so we created a nice bridging class
    that will take our Model and will format it nice and pretty for the
    user's consumption.
    OK, everything is taking shape, we are following one of the
    patterns that all the cool kids are using this days and then comes
    the part where we need to consume the data from the API.
    And a pinch of ViewModels
    class HopsViewModel {
    // Model
    private let model: Hop
    // Properties
    var name: String {
    return model.name ?? "N/A"
    }
    var amount: String {
    // Notice how here we should convert to the user's unit (imperial / metric)
    return "\(model.amount) g"
    }
    init(withHop hop: Hop) { … }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  14. Sure thing, just a matter of creating an API consumer, using
    NSURLSession or Moya or AlamoFire, you can choose your
    poison.
    And we put it out there kind of like a helper function (because
    come on people, we have «Model, View, ViewModel») the pattern
    is not called «Model-View-ViewModel-Network».
    Network layer
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  15. So… is it «kosher» to have the layer just floating in there, let's say
    yes. Most of us that have coded at least 1 app have done this so
    yeah. Let's throw the folder of networking and put our class there
    and quick and easy we have everything ready.
    We have a really nice separation of concerns… do we?
    Technically speaking our models should be agnostic in terms of
    where the data comes from so… the JSON parsing shouldn't be
    inside of it… (Although I'm guilty here, I like having my JSON parsing
    per class !)
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  16. And then… where should we make the API calls? Obviously the real
    network call will happen on our network layer but then again; who
    should be responsible of telling our network layer to hit the server?
    Where do we !
    things now❓
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  17. Should we put this on the ViewController and pass the
    response to the MVVM so that everything gets parsed?
    Don't know about you but that sounds to me like a convoluted
    mess and I think we are violating 1 or 2 «laws» of the separation
    of concerns and committing 2 or 3 atrocities against nature.
    !
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  18. I know !!
    Let's put the calls on the ViewModel; we clean up our
    ViewControllers, we signal our ViewModel from the VC and the
    VM signals our network layer and then we process everything on the
    ViewModel and throw that back to our VC where the View will take
    charge of displaying the data to the user
    !
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  19. class ViewModelA {
    private let model: Model
    var formattedCurrency: String…
    var formattedProperty1: Double…
    var formattedProperty2: String…


    init(withModel model: Model) …
    func getModels(closure:([Model]) -> ()) -> Void {
    API.request(.GetModels) { jsonResponse in
    // Parse the JSON response here
    let models = …
    closure(models)
    }
    }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  20. While this might clean up our VC I personally think that we are still
    not having a clear separation of concerns… Let's go back to our
    definition of ViewModel
    !
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  21. It should format the data from the Model; not actually get the data from our source, parse it to generate our models and then
    format it… sounds like we are putting too many responsibilities unto our VM (not to mention that we are «creating» the models
    from the viewmodel which in theory is a bridge, in this case a bridge from nothing to the view but in the middle it builds the
    other side of that bridge while being the bridge itself… ! exactly) the and we are moving from «Massive View Controller» to
    Model-VeryBigViewModel-View (for the love of god laugh here because I couldn't come up with a better «joke» ")
    MVVM
    …an abstraction of the view exposing
    public properties and commands… In the
    view model, the binder mediates
    communication between the view and the
    data binder.
    The view model has been described as a
    state of the data in the model.1
    1 http://wayback.archive.org/web/20080201101909/http://
    www.acceptedeclectic.com/2008/01/model-view-viewmodel-pattern-for-
    wpf.html
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  22. So; how should we approach this you say?
    With a rub of this magical tonic I'll be selling
    later and the use of Data Controllers.
    Brewbot's approach
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  23. I would love to take the credit about the use of Data Controllers
    so I will… although it was actually our CTOwho implemented them.
    When I first joined Brewbot the DataControllers where already
    there and they just made sense (mind you we didn't have MVVM back
    then though)
    Like I said earlier I'm a proponent of MVVM and first thing I mentioned
    when I joined was that we should start using it; it would help us clean
    up the code and will make things easier to maintain and pretty much
    recite the whole part of Google's-great-reasons-to-use-MVVM and
    his response was this:
    DataControllers
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  24. I'm also a believer of Reactive programming. Mind you I can understand just a
    certain part of it and some of the operators still elude my comprehension but
    that doesn't stop me from understanding its benefits for a certain type of
    projects and didn't stop me from realizing that our app at Brewbot was one of
    those projects.
    The framework that you choose won't matter much in the big scheme of
    things… just read carefully each one and go with the one you feel most
    comfortable with.
    Back in 2011 or 2012 I was flying to a certain gathering of developers called
    dub-dub and I bought a book for the flight. The name of the book was
    `Functional Reactive Programming by Ash Furrow.
    In this book he explains fundaments about FRP and also ties everything
    together with the use of MVVM; that was my first approach at MVVM and I was
    sold.
    More importantly at the end of the book there's a chapter about
    ReactiveCocoa and MVVM with a μFramework called ReactiveViewModel.
    I was using ReactiveCocoa at the time so everything started to make sense
    in my brain and I decided to give it a try and it was great. (not so much though,
    had to write Ash a couple of emails and bug him on Twitter to clarify a couple
    of things, but him being the great human being that he is answered every one
    of my questions and helped me understand a topic which feels me with passion
    One more thing…
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  25. In 2014  gave us Swift and great devs out there
    like Krunoslav Zaher created RxSwift and
    RxCocoa.
    I liked the idea of sharing concepts with other
    languages (RxJava, Rx… ) and that
    cause the creation of RxViewModel.
    And that was a HUGE introduction to tie with my talk.
    The age of Swift
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  26. Finally!!
    ^ Ok, we have covered MVVM, we covered
    DataControllers and now is time to glue
    everything with RxSwift.
    MVVM
    +
    RxSwift
    +
    DataControllers
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  27. For those of you that don't know or don't have too clear what
    Reactive Programming is, let's just think that data is a stream and
    you can tap unto some sinks and «listen» to that stream.
    So whenever new data comes down the stream you'll get it almost
    instantly and you can «react» (get it?) to it.
    It's a really nice way to bind everything together, for example a
    login form can be easily «glued» with Reactive programming so
    that the validation happens reactively and without having to go all
    over the place checking for each data that we need.
    RxSwift
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  28. When I first joined we had KVOController to handle most of this, worked in a way
    but was kind of slow and everything came down the same sink, you had to select
    what went where and when we should do something depending on which event was
    firing
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  29. Instead we now have most of the app connected using
    RxSwift and RxCocoa.
    This help us detach some of that «funnel like» pattern
    and instead separate the different streams based on the
    signal that emits them.
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  30. Here's a basic graph/model of how we set it up:
    As you can see our VCs hold a reference to a
    ViewModel for that specific view, this VM is of the
    reactive type so we hook up the active signal in
    order to correctly react to our active state change.
    Inside of it we hold a reference to a data controller
    specific to the task at hand:
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  31. Moving lower into the chain we have the data controller that's in charge of
    actually calling our netwokr layer (we don't need to dwell into the network
    layer, pretty basic-bolierplate-y code there.
    In our Data Controller we handle the responses from the network layer and
    bubble them up to the VM after correctly parsing it and creating the correct
    models that are needed.
    Now we are back at the VM where we just need to format our data
    accordingly to our specification, handle some of the pagination logic (since
    to me at least that's tied to the presentation layer given that you need to
    «format» the data accordingly to the page you are currently in) and fire up
    the results on the appropiate signal.
    Depending on your logic you can defer turning off the signal either at the
    very beginning of the chain or just before returning a set of results or errors.
    class ViewModel {
    // Hold a reference to our DataController
    private let dataController: DataController
    // Hold a reference to the model we will be getting
    private var model: Model?
    func getModel(id: Int) -> Observable {
    return Observable.create { observer in
    dataController.getModel(id)
    .subscribe(onNext: { [unowned self] model in
    self.model = model
    // If we need to format something else we should do it here
    observer.onNext(self.model)
    }, onError: { error in
    // Maybe properly format the error to the user here as well
    observer.onError(error)
    }
    ).addDisposableTo(disposeBag)
    }
    }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  32. Stepping out of the VM we get back to the VC which, depending
    on how you connected everything needs to either reflect the
    changes from the VM into the UI or simply do nothing because
    everything is tightly connected via Rx logic.
    And if we go back to the VC we can update our UI and be done
    with our lives.
    class ViewController: UIViewController {
    // Retain a reference to our ViewModel
    private let viewModel: ViewModel
    func loadData() -> Void {
    self.viewModel.loadModel()
    .subscribeOn(MainScheduler.instance)
    .subscribe(onNext: { [unonwed self] viewModel in
    // Update your `UI` accordingly
    self.nameLabel.text = viewModel.formattedName
    self.amountLabel.tet = viewModel.hopsAmount
    }, onError: { error in
    }
    })
    .addDisposableTo(disposeBag)
    }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  33. If we think of bindings from a REALLY simplified perspective they are just «automagical»
    ways to connect to parts, they could be objects, structs, UI components and since they are
    connected they react as soon as one changes (or both depending on how you bind them).
    Rx Bindings
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  34. For example here
    class ViewController: UIViewController {
    // Retain a reference to our ViewModel
    private let viewModel: ViewModel
    // Binding function
    override func viewDidLoad() {
    super.viewDidLoad()
    self.bindComponents()
    }
    }
    private extension ViewController {
    func bindComponents() -> Void {
    self.viewModel
    .formattedName
    .bindTo(self.nameLabel.rx_text)
    .addDisposableTo(disposeBag)
    self.viewModel
    .hopsAmount
    .bindTo(self.amountLabel.rx_text)
    .addDisposableTo(disposeBag)
    }
    }
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  35. Let's recapitulate, we realized that MVC as well as MVVM don't
    really detail where we should put the responsibilites of calling
    the network.
    We covered why/how DataControllers help us clean up
    that mess by takinng up the role of actually handling that
    portion of the flow.
    We glued everything together with a «reactive» approach to
    better handle an app with constant and multiples inputs of
    information that need to be presented to the user.
    What did we
    accomplished?
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  36. I'm a big fan of adding unit tests, integration
    tests, acceptance tests, test all the things!!
    Last time I gave this talk I was asked how did
    we test this approach
    Testing
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  37. What we ended up doing @ Brewbot was using a DI
    approach.
    That way you can actually change or stub your behavior
    to be able to actually test it in a quick and easy way.
    How do we test
    this?
    Dependency Injection
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  38. Obviously there are some cons to using this approach.
    Adds a little bit of «complexity» due to the «extra»
    layers.
    Using a reactive framework is a though choice because
    makes the curve for new developers joining a little bit
    harder and it will also «bleed» through all the layers,
    then changing or doing major upgrades is a painful.
    Are there any
    Cons?
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  39. Just as some parting words; we do not claim to have
    created yet another architecture or to have patched
    MVVM in any way.
    ^ I thought that our solution is nice enough and works
    good enough to be shared with the world.
    Why?
    Maybe there are some other devs out there asking
    themselves the same questions about where to put
    certain calls, maybe this will be for them like an epiphany
    and they'll be like:
    Conclusion
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  40. Which is a perfectly fine (and rather harsh) response. We just wanted to give
    you some brain food and maybe pique your interest in adventuring in using
    a reactive approach next time you people need to tackle a new challenge.
    .
    "That was the worst idea EVER…
    BUT seeing how badly they tackled that
    problem gave me an idea about how to
    actually make it work."
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide

  41. Thanks
    Esteban Torres
    https://estebantorr.es
    @esttorhe
    #makeBorisOSSAgain
    Esteban Torres - @esttorhe, NSSpain, 2016

    View Slide