MVVM + RxSwift + DataControllers

F2f5f7bc8bd3bd71e51d303e9881fe78?s=47 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

F2f5f7bc8bd3bd71e51d303e9881fe78?s=128

Esteban Torres

September 16, 2016
Tweet

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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  11. Let's make some UI Esteban Torres - @esttorhe, NSSpain, 2016

  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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… <add more>) 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
  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
  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
  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
  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
  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
  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<Model> { 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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  41. Thanks Esteban Torres https://estebantorr.es @esttorhe #makeBorisOSSAgain Esteban Torres - @esttorhe,

    NSSpain, 2016