How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API

2,362 views

Published on

Learn how our team built a cross-platform mobile electronic health record (EHR) app from scratch in less than a year using Xamarin, Angular, and Web API.
Discover the reason we chose the mix of technologies (Xamarin, Angular, Web API) we did.
You will get an overview of the implementation of the entire application from the front end to the back end. There will be plenty of app demos and code samples.
What will be covered:
• The best way to build a cross-platform app
• How we combined native Xamarin UI elements with Angular based web views
• Our custom JavaScript bridge used to communicate between our Angular and Xamarin modules
• Lessons learned about cross-platform app development
• Automated E2E testing on devices using Xamarin Test Cloud
• Back end implementation using ASP.NET Web API

Published in: Software
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,362
On SlideShare
0
From Embeds
0
Number of Embeds
20
Actions
Shares
0
Downloads
42
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API

  1. 1. How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API
  2. 2. About Matt Spradley • Sr. Product Manager at Aprima • Co-founded 3 software companies • Used to write code • Tinkers every now and then • linkedin.com/in/mattspradley • mattspradley.com • matt.spradley@gmail.com • 214-403-6749
  3. 3. How Should we Build a Cross Platform Mobile App? http://imgs.xkcd.com/comics/efficiency.png
  4. 4. Requirements • Tablet 90% functional parity with desktop EHR • Work on iOS and Android phones and tablets • Integrate with on-premise servers • No control over network (DNS, Firewall, etc.) • Multiple versions of REST API
  5. 5. Test (POC) Don’t Guess
  6. 6. …And the Results Score 66 64 63 53 Factor Weight HTML/Steroids HTML/Titanium Native/Xamarin Native UX 3 2 2 3 3 Code Reuse 3 3 3 2 1 Special UI 1 1 2 3 3 Control 1 2 2 3 3 Effort 3 3 2 2 1 Maintenance 3 2 2 3 2 Cloud Reuse 1 3 3 1 1 OS Integration 2 2 2 3 3 Deployment Options 1 3 3 1 1 Existing Dev Skills 1 2 2 3 1 Hiring 2 2 2 3 2 Vendor Stability 3 2 2 2 3 UTDesign Decision 1 2 2 1 1 UI Testability 3 3 3 1 1 Winner???
  7. 7. Web UI with Angular Is Easy <ul id="appointments" class="list-group list-group-fixed-height“ collapse="tileState.isCollapsed"> <li class="list-group-item" ng-show="!loading && appointments.length == 0"> <div class="col-xs-12 text-center-vert text-center"> <span translate>No appointments</span> </div> </li> <li ng-repeat="appt in appointments | orderBy:'date'" data-transition-to="{ appRoute: 'patientDashboard', opts: {id: appt.PatientId}}" class="list-group-item" ng-class="{'active': (!!appt.clicked)}"> <ap-appointment-list-item data-appt="appt" data-context-menu-id="appt-context-menu-{{$index}}"> </ap-appointment-list-item> </li> </ul>
  8. 8. <div class="panel-body panel-flush" collapse="tileState.isCollapsed" ng-if="!loading && !httpError"> <table class="table table-striped ap-table"> <thead> <tr> <th translate>Name</th> <th ng-repeat="column in vitals.Columns | limitTo:observationLimit"> <ap-date-formatter date="{{column.ObservationDateTime}}“ format="short-date"></ap-date-formatter> </th> </tr> </thead> <tbody> <tr ng-show="vitalsExist" ng-repeat="observations in vitals.Rows | filter:hasValueForColumns"> <td>{{observations.Name}}</td> <td ng-repeat="observation in observations.Observations | limitTo:observationLimit" ng-class="{danger: !observation.IsInRange && observation.Value}" class="vital-value"> {{observation.Value}} </td> </tr> <tr ng-if="!vitalsExist"> <td> <span id="no-vital-results" translate> No known vitals. </span> </td> </tr> </tbody> </table> </div> Web UI with Angular Is Easy, Really
  9. 9. Code and Build • Web UI • Code in HTML, Less, AngularJS • Build • Grunt • Less • Template process • JsHint • Uglify • Unit Tests w/ Jasmine and Karma • Deploy • Translate • Images to Less
  10. 10. Chocolate and Peanut Butter Our Customers LOVE Aprima NOW
  11. 11. Go Native with Xamarin for Hard Stuff • For • Taking Pictures • Image Annotation • PDF Viewer • Why • Flexibility • C# • Performance • Custom UI • Look and Feel • Services • Hiring
  12. 12. Xamarin Forms Demo
  13. 13. Aprima NOW Hybrid Architecture Javascript/HTML JS <--> Xamarin(C#) Bridge Xamarin(C#) AngularJS App JS Bridge Common (portable class library) Xamarin.Android Xamarin.iOS Fire Event Subscribe SubscribeHandle Events Handle Events Fire Event or Subscribe Invoke Callback from Subscription AppServer
  14. 14. Javascript Bridge Angular Service, Ctrl etc iOS JS Bridge Android JS Bridge WinForms JS Bridge Fire Events to C#. Subscribe to Events from C# iOS C# Bridge Android C# Bridge WinForms C# Bridge XHR to app:// window.AndrApp.Event(eventData); window.external.HandleEvent(eventData) C# Objects Fire Events to JS Subscribe to Events from JS webview.EvaluateJavascript(jsToFireEvent); Webview.LoadUrl( javascript:{jsToFireEvent} ); Webview.Document.InvokeScript( eval , jsToFireEvent ); Handle events from C# Handle events from JS
  15. 15. Jsbridge.js • JS object provides bridging capabilities to JS code • Fire events to C# • Add event handlers in JS to be called when event is fired from C# • Bridge implementations for iOS, Android, and WinForms • Normalizes event structure for consistency: app://{module}/fireEvent?data={jsEventDataJson}&_={random}
  16. 16. C# -> JS event Firing • To fire an event, call the following. BridgeContext is a bridge which is scoped to the UI WebView component (iOS: UIWebView; Android: WebView; WinForms: Form/Control/etc) this.BridgeContext.FireEvent(eventName, data); • Which calls the following within the BridgeContext instance and actually dispatches the event to javascript. ExecuteScript is implemented differently by each platform. ExecuteScript(string.Format("bridge.app._dispatchEvent('{0}', {1});", eventName, json));
  17. 17. iOS Jsbridge.js • To send an event from JS to C#, iOS uses a custom NSUrlProtocol implementation which listens for XHR traffic on a custom protocol (“app://”). • To send event from JS to C#, an XHR request is made to custom protocol with event data in the url. function (url) { var x1 = new bridge.app.CustomXhr(); x1.onerror = function (e) { console.log('XHR error:' + JSON.stringify(e)); }; x1.open('GET', url); x1.send(); }
  18. 18. iOS C# Bridge • Registers global NSUrlProtocol handler, takes an instance of UIWebView and bind global events to specific UIWebViews. • To send event from C# to JS, executes dynamically generated JS which calls into jsbridge.js component. public override void ExecuteScript(string javascript) { webView.BeginInvokeOnMainThread(() => _webView.EvaluateJavascript(javascript)); }
  19. 19. iOS JavaScript Bridge Demo
  20. 20. Android jsbridge.js • To send an event from JS to C#, Android uses a Java object which is made accessible as a JavaScript object by adding the object instance to the Webview as a JavaScript Interface. • To send event from JS to C#, a method on the Java object instance is invoked from JavaScript. function (url){ var result = window.AndrApp.Event(url); result = JSON.parse(result); if (!result.Success) { console.log('Android bridge error: ' + JSON.stringify(result)); } }
  21. 21. Android C# bridge • Instantiates and adds java object with [Export] or [JavascriptInterface] members. Members are accessible from javascript. webView.AddJavascriptInterface(new JsBridgeExports(this), "AndrApp"); • To execute a script, it loads a url which contains javascript to be executed: public override void ExecuteScript(string javascript) { activity.RunOnUiThread(() => _webView.LoadUrl(string.Format("javascript: {0}", javascript))); }
  22. 22. WinForms jsbridge.js • To send an event from JS to C#, WinForms uses a C# object which is made accessible as a Javascript object by adding the object instance to the WebView as the ObjectForScripting. • To send event from JS to C#, a method on the C# object instance is invoked from javascript. function (url) { var result = window.external.HandleEvent(url); result = JSON.parse(result); if (!result.Success) { console.log('PRM bridge error: ' + JSON.stringify(result)); } }
  23. 23. WinForms C# bridge • Instantiates and adds C# object to the WebBrowser instance as the ObjectForScripting. ObjectForScripting must be have[ComVisible(true)] this._browser.ObjectForScripting = new JsBridgeWindowExternalHandler(this); To fire event from C# to JS, eval function is invoked from C# with a IIFE (Immediately Invoked Function Expression) public override void ExecuteScript(string javascript) { _browser.Invoke(new Action(() => { if (_browser.Document != null) { var script = string.Format("(function() {{ {0}; }})();", javascript); _browser.Document.InvokeScript("eval", new object[] { script }); } })); }
  24. 24. JS Code to fire and listen for events angular.module('amodule').controller('SomeCtrl', ['$scope', 'Bridge', function ($scope, Bridge){ //fire event Bridge.navigate('aRoute', { id : 0 }); //listen for event Bridge.on('someEvent', function (data) { $scope.data = data; }); }] );
  25. 25. C# code to fire and listen for events //Fire Event this.BridgeContext.FireEvent("navigate", new { id = 0}); //listen to event this.BridgeContext.AddEventListener("someEvent", (SomeType data) => { //do something with data });
  26. 26. Notes • Bridge lifetimes vary by platform. • iOS has a singleton bridge because NSProtocolHandler is added for an entire application instead of for a specific UIWebView. iOS bridge has logic to broker events to the correct bridge context which isassociated with a specific UIWebView. • Android bridges are instantiated per WebView instances • WinForms bridges are instantiated per WebBrowser instance • Native components must tell JS bridge which native implementation is used. This can happen AFTER events have already been fired from JS. This required queuing of events until the bridge was finished being setup.
  27. 27. Notes • Majority of code is in a PCL library and reused by all platforms • Dll exists for each platform that contains platform specific code • Very little platform specific code • Bridge behavior consistent across the platforms
  28. 28. Frontend Tech Stack • Xamarin • Angular • Bootstrap • lodash • Less • Hammer.JS
  29. 29. Testing http://www.idyllic-software.com/blog/category/ruby-on-rails/page/5/
  30. 30. Web UI is Easy to Test and Debug Grunt Jasmine Protractor Webdriver E2E
  31. 31. Device Testing with Xamarin Test Cloud
  32. 32. Xamarin Test Cloud Testing [Test] public void PinchToZoom() { LoginPage.Login(app); app.WaitForElement(x => x.Css("#dashboard")); app.Screenshot("Then I am logged in at my home screen"); app.GoToPatients(); QuicksearchPage.SearchForPatient(app, "Anderson"); QuicksearchPage.SelectPatient(app, "e2ab0790-f271-471a-bdc2-e6bca0889dad"); app.WaitForElement(x => x.Css("#patient-widgets"), "Timed out waiting for patient dashboard to load", new TimeSpan(0, 0, 3)); app.Screenshot("Then I see Jeff's profile"); PatientDashboardPage.ExpandWidget(app, PatientDashboardWidget.ObservationResults); PatientDashboardPage.TapObservationResult(app, "3f39a0fa-99e9-494b-a234-170f3ff824ba"); Thread.Sleep(5000); app.Screenshot("Now I should see the images"); //Scroll down to view first image app.ScrollDownEnough(x => x.WebView().Css(".image-viewer")); var rect = app.Query(x => x.WebView().Css(".image-viewer"))[0].Rect; app.Zoom(rect.CenterX, rect.CenterY, 100); app.Screenshot("Now I zoom in on the image"); }
  33. 33. Not All Web Browsers are Equal
  34. 34. iOS and Android Webview Update Differences
  35. 35. Inertial Scroll or EasyScroller • Safari: Fixed DIVs Bad • https://github.com/zynga/scroller
  36. 36. Xamarin Issues • Constant updates • Things break • Universal API kerfuffle • IDE lockups • They’re still awesome and smart http://www.doomsteaddiner.net/blog/wp-content/uploads/2013/04/wheels-off-hummer.png
  37. 37. Backend • Web API • OWIN • Azure Relay • SQL Server • Feature List for Versions • NEO (home brew ORM)
  38. 38. REST Facade App Server UI 1 UI 2 DBEF Other Aprima Mobile Application Architecture Decoupled deployment from Aprima Main
  39. 39. On-Premise Server
  40. 40. Azure Relay WebHttpBinding A Developer’s Guide to Service Bus in Windows Azure Platform
  41. 41. Service Bus Relay Web API Host https://pfelix.wordpress.com/tag/asp-net-web-api/
  42. 42. Relay Demo • By pass on-premise issues • … and pesky IT road blocks http://imgs.xkcd.com/comics/security.png
  43. 43. Azure Relay Performance -100 0 100 200 300 400 500 600 700 800 Azure Ping1 (ms) Relay Ping1 (ms) Azure Ping2 (m2) Relay Ping2 (m2) Azure 5K (ms) Relay 5K (ms) Azure 10k (ms) Relay 10k (ms) Azure 50K (ms) Relay 50K (ms) Azure 500K (ms) Relay 500K (ms)Average
  44. 44. Code Metrics • Backend ≈ 50K lines • 87% C# • 10% Build scripts • 3% Other • Frontend ≈ 100K lines • 59% JavaScript • 30% HTML (HTML, CSS, Less) • 8% C# (90% in common PCL) • 3% Build scripts
  45. 45. It Works
  46. 46. Thanks to a Great Team • Mobile Team • Ryan Cady • Kenneth Crawford • Mike Duran • Jeff Lott • Contributors • Doug Jost • Chris Mojica • Karl Shearer • Design • More Simple
  47. 47. Top Ten Rules of Software Development 1. Order the T-shirts for the Development team 2. Announce availability 3. Write the code 4. Write the manual 5. Hire a Product Manager 6. Spec the software (writing the specs after the code helps to ensure that the software meets the specifications) 7. Ship 8. Test (the customers are a big help here) 9. Identify bugs as potential enhancements 10. Announce the upgrade program http://www.nullskull.com/a/722/the-top-ten-rules-of-software-development.aspx
  48. 48. Links • http://xamarin.com/ • https://angularjs.org/ • https://github.com/crdeutsch/MonoTouch-JsBridge • http://zynga.github.io/scroller/ • https://pfelix.wordpress.com/tag/asp-net-web-api/ • Designing Evolvable Web APIs with ASP.NET • https://github.com/pmhsfelix/WebApi.Explorations.ServiceBusRel ayHost • www.moresimple.com

×