( And Halp clients )Code
Reusewith MVVM1Dienstag, 16. April 13High-level talk, mostly focused on Apple (because that’s my forté), but most of the concepts here are generally applicable to allplatforms (and that’s exactly the point).
WhoJustin Spahr-Summers - @jspahrsummersPaul Betts
- @xpaulbettsxJosh Vera - @joshveraJosh Abernathy - @joshaber3Dienstag, 16. April 13I work on GitHub for Mac. I primarily write Objective-C code, but I also regularly code in Haskell as well. Paul works on GitHub forWindows (written in C#), Josh Vera works on an internal talks app written in Objective-C, and Josh Abernathy also works onGitHub for Mac.There’s quite a large diversity of experience between us, and I’ve included these guys on here because they all had something dowith this presentation today.
WhatWrite unit tests for UI
behaviorExample: our native Halp appsMaximize code reuse across platforms,but keep 100% native UI4Dienstag, 16. April 13Halp is an internal app that we use at GitHub for user support (but more on that later).
Why“Build software better, together.”At GitHub,
we ask: “What is the best way to build software?”Philosophies, tools, practices5Dienstag, 16. April 13One of the questions _we_ work on every day is, “What’s the best way to build native user interfaces?”
View ModelControllerHow( According to
)6Dienstag, 16. April 13As recommended by Apple, Cocoa applications are typically designed using Model-View-Controller, shown here. The solid linesrepresent direct references; the dashed lines represent indirect references (like observation).
View ViewModel ModelHow( According to
us )7Dienstag, 16. April 13At GitHub, we much prefer to use Model-View-ViewModel, shown here. If you haven’t been introduced to MVVM, here’s a quickexplanation:The ViewModel replaces the role of the (View) Controller, but the VM doesn’t have a direct reference to the view like a controllerwould. Instead, the VM communicates to the V with a system of bindings. … For example, If you want to show a loading spinner,the view model might have a boolean property which indicates whether to show it. The view would observe that property forchanges, and hide/show the spinner in response.
Meh. So what?8Dienstag, 16. April
13This might just seem like a way to restate the MVC pattern, but the reversed relationship between the View and the ViewModeloffers huge beneﬁts.
Benefits of MVVM✓ View models
are testable, no UIautomation required✓ View models can do model-likethings (e.g., serialization)9Dienstag, 16. April 13Traditionally, view controllers rarely get unit tested in Cocoa, simply because it’s such a pain to write a controller that doesn’tdepend on having a view (or, alternatively, to set up a valid view in unit tests). Since the VM doesn’t even know about viewobjects, they can be tested without a GUI at all!Serialization: for example, to save and restore the state of your UI, you can just save and restore your VM hierarchy. Doing this inMVC would require a separate set of “view state” objects – which are basically view models anyways!
What’s Unique?We want 100% native
UI on eachplatform – no Qt, GTK+, or JavaTo do this, we need to create viewsspecific to each platform13Dienstag, 16. April 13This makes sense and is perfectly appropriate. Sharing view code leads to lower-quality apps which cater to the lowest commondenominator and ignore each platform’s individual UI conventions.
What’s Unshared?Logic for when to
fetchresources from the APIUI behaviors(e.g., how to populate lists, or when to show spinners)14Dienstag, 16. April 13If we follow MVC, we’re also rewriting this logic for each platform (as part of our controller layer), even though it’s not platform-speciﬁc. This is code that _should_ be shared, but isn’t.Now, let’s contrast that with MVVM.
Model-View-ViewModelView ViewModel Model15Dienstag, 16. April
13Interestingly, because the VM doesn’t reference the view (or any UI) directly, it becomes reusable across platforms. The VMdescribes only how the UI should update and respond to user actions – not how it should look. Multiple types of view can becreated for one view model, and each can look completely different, but most of the underlying logic will remain the same.If we’re using Xamarin, we can now write most of our model _and view model_ code just once. The VM implements most of ourUI behavior, like…
View Models Handle…Loading content the
UI needsHiding and showing contentDate, number, and string formattingResponding to the user16Dienstag, 16. April 13There are just some typical use cases, not a complete list.Loading: note that the view model is not actually responsible for the details of persistence, networking, etc. It’s only responsiblefor communicating with whatever that layer is, _based on_ what the UI needs to show at any point in time.
Halp!17Dienstag, 16. April 13That’s most
of the abstract stuff. I want to switch gears for a moment here and talk about our support tool, and the native clientswe’re implementing using MVVM and Xamarin.
18Dienstag, 16. April 13This is
the web app that we use for user support. It lets us triage our users’ emails and get them to the right people as quickly aspossible. Supportocats and developers can reply to messages, bring other people into the discussion, cross-link to other internalresources, etc.Here, we’re looking at a discussion thread in the Technical inbox.
19Dienstag, 16. April 13GitHub is
based in San Francisco, but about half of GitHub works remotely on a regular basis (I myself work from Seattle). Twiceevery year, all of the company meets in SF for GitHub Summit.Our last summit was earlier this year, and a few of us wanted to spend our Hack Day working on a native client for Halp. Wedecided to use Xamarin to share code between our different desired platforms, and reduce the development and maintenanceeffort that would otherwise be involved in each one. We started the iOS client that day, and a Mac client since.
Mac App GoalsWatch a specific
inbox for new messagesDisplay a message count in the menu barView the messages in any inbox(but especially the watched one)20Dienstag, 16. April 13This is what we want to do for our Mac client.We’ve started on a prototype. It’s still very premature, so it doesn’t do much yet.
iPhone App GoalsView the messages
in any inboxRead any messageTriage messages by moving to another inbox21Dienstag, 16. April 13And this is what we want to do for our iPhone client. (An iPad client would be very similar as well.)This one’s a bit further along, but still pretty rough around the edges. All the data here is loaded from the API and cached locallyby the app.
Shared BehaviorsShowing inboxes and messagesRequesting
and caching dataShowing loading indicators23Dienstag, 16. April 13By no coincidence, these are the behaviors implemented by our cross-platform view models.Let’s take a look at the code. (ViewModels, MenubarController?, PopoverController?, TableSources)
View ControllersLayout, animations, device rotation,view
transitionsSeems like view controllers areactually part of the view layer!26Dienstag, 16. April 13OS X and iOS both have view (or window) controllers, which can make MVVM confusing at ﬁrst glance. Once you look deeper,though, it’s not much of a problem at all.
View ControllersNSViewController doesn’t do muchUIViewController
is quite powerfulBetween views and view controllers,use the easiest one27Dienstag, 16. April 13Basically, use the class that will make implementing your view layer easiest. On OS X, you’ll probably just want NSView, sinceNSViewController is relatively useless. On iOS, you’ll probably want UIViewController, so you can handle rotation, navigation, etc.No matter what you decide to use for your UI, you’ll still have a ViewModel.
Data BindingNotifications are too general,and
have global scopeKey-Value Observing is difficultto use and comes with boilerplate28Dienstag, 16. April 13It’s hard to write the indirect relationship from the ViewModel to the View without a powerful system of bindings. Cocoa (and, byextension, Xamarin.Mac and Xamarin.iOS) offers a couple solutions, but they’re woefully inadequate.In addition to these individual problems, neither supports automatic transformation or ﬁltering of bound values. Worse, both arespeciﬁc to Cocoa, so our V <> VM bindings will look quite different from our VM <> M bindings (which should be cross-platform).
Data BindingIn Objective-C, we wrote
aframework called ReactiveCocoaIn C#, we have Reactive Extensionsand our ReactiveUI framework29Dienstag, 16. April 13Reactive Extensions (or Rx) is an implementation of Functional Reactive Programming, which is unfortunately beyond the scopeof this talk, but there are lots of great resources for learning more about it.ReactiveUI is an MVVM framework for .NET. One of its major features is an API for declarative data bindings, built on top of Rx.
30Dienstag, 16. April 13GitHub for
Mac uses ReactiveCocoa to implement MVVM at a large scale. The app itself is written in Objective-C, but the lessonswe’ve learned about MVVM are just as applicable to Xamarin.Mac and Xamarin.iOS.