How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API
-
Upload
matt-spradley -
Category
Software
-
view
2.467 -
download
0
Transcript of How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API
PowerPoint Presentation
How We Built a Mobile Electronic Health Record App Using Xamarin, Angular, and Web API
1
About Matt SpradleySr. Product Manager at AprimaCo-founded 3 software companiesUsed to write codeTinkers every now and thenlinkedin.com/in/mattspradley [email protected]
2
3
How Should we Build a Cross Platform Mobile App?
http://imgs.xkcd.com/comics/efficiency.png
RequirementsTablet 90% functional parity with desktop EHRWork on iOS and Android phones and tabletsIntegrate with on-premise serversNo control over network (DNS, Firewall, etc.)Multiple versions of REST API
Test (POC) Dont Guess
And the ResultsScore66646353FactorWeightHTML/SteroidsHTML/TitaniumNative/XamarinNativeUX32233Code Reuse33321Special UI11233Control12233Effort33221Maintenance32232Cloud Reuse13311OS Integration22233Deployment Options13311Existing Dev Skills12231Hiring22232Vendor Stability32223UTDesign Decision12211UI Testability33311
Winner???
Web UI with Angular Is Easy
-
{{observations.Name}} {{observation.Value}} No known vitals.
Web UI with Angular Is Easy, Really
Code and BuildWeb UICode in HTML, Less, AngularJSBuildGruntLessTemplate processJsHintUglifyUnit Tests w/ Jasmine and KarmaDeployTranslateImages to Less
Chocolate and Peanut Butter
Our Customers LOVE Aprima NOW
Go Native with Xamarin for Hard StuffForTaking PicturesImage AnnotationPDF ViewerWhyFlexibilityC#PerformanceCustom UILook and FeelServicesHiring
Xamarin Forms Demo
Aprima NOW Hybrid ArchitectureJavascript/HTMLJS Xamarin(C#) BridgeXamarin(C#)AngularJS AppJS BridgeCommon (portable class library)Xamarin.AndroidXamarin.iOSFire EventSubscribeSubscribeHandle EventsHandle EventsFire Event or SubscribeInvoke Callback from SubscriptionFire EventFire EventAppServer
Javascript Bridge
Jsbridge.jsJS 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 WinFormsNormalizes event structure for consistency:
app://{module}/fireEvent?data={jsEventDataJson}&_={random}
C# -> JS event FiringTo 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));
iOS Jsbridge.jsTo 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();}
iOS C# BridgeRegisters 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));}
iOS JavaScript Bridge Demo
Android jsbridge.jsTo 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)); }}
Android C# bridgeInstantiates 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)));}
WinForms jsbridge.jsTo 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)); }}
WinForms C# bridgeInstantiates 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 }); } }));}
JS Code to fire and listen for eventsangular.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; }); }]);
C# code to fire and listen for events//Fire Eventthis.BridgeContext.FireEvent("navigate", new { id = 0});
//listen to eventthis.BridgeContext.AddEventListener("someEvent", (SomeType data) => { //do something with data });
NotesBridge 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 instancesWinForms bridges are instantiated per WebBrowser instanceNative 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.
NotesMajority of code is in a PCL library and reused by all platformsDll exists for each platform that contains platform specific codeVery little platform specific codeBridge behavior consistent across the platforms
Frontend Tech StackXamarinAngularBootstraplodashLessHammer.JS
Testing
http://www.idyllic-software.com/blog/category/ruby-on-rails/page/5/
Web UI is Easy to Test and Debug
Device Testing with Xamarin Test Cloud
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");}
Not All Web Browsers are Equal
iOS and Android Webview Update Differences
Inertial Scroll or EasyScrollerSafari: Fixed DIVs Badhttps://github.com/zynga/scroller
Xamarin IssuesConstant updatesThings breakUniversal API kerfuffleIDE lockupsTheyre still awesome and smart
http://www.doomsteaddiner.net/blog/wp-content/uploads/2013/04/wheels-off-hummer.png
BackendWeb APIOWINAzure RelaySQL ServerFeature List for VersionsNEO (home brew ORM)
On-Premise Server
Azure Relay WebHttpBinding
A Developers Guide to Service Bus in Windows Azure Platform
Service Bus Relay Web API Host
https://pfelix.wordpress.com/tag/asp-net-web-api/
Relay DemoBy pass on-premise issues and pesky IT road blocks
http://imgs.xkcd.com/comics/security.png
Azure Relay Performance
Code MetricsBackend 50K lines87% C#10% Build scripts3% OtherFrontend 100K lines59% JavaScript30% HTML (HTML, CSS, Less)8% C# (90% in common PCL)3% Build scripts
It Works
Thanks to a Great TeamMobile TeamRyan CadyKenneth CrawfordMike DuranJeff LottContributorsDoug JostChris MojicaKarl ShearerDesignMore Simple
Top Ten Rules of Software Development Order the T-shirts for the Development teamAnnounce availabilityWrite the codeWrite the manualHire a Product ManagerSpec the software (writing the specs after the code helps to ensure that the software meets the specifications)ShipTest (the customers are a big help here)Identify bugs as potential enhancementsAnnounce the upgrade programhttp://www.nullskull.com/a/722/the-top-ten-rules-of-software-development.aspx
Linkshttp://xamarin.com/https://angularjs.org/https://github.com/crdeutsch/MonoTouch-JsBridgehttp://zynga.github.io/scroller/https://pfelix.wordpress.com/tag/asp-net-web-api/Designing Evolvable Web APIs with ASP.NEThttps://github.com/pmhsfelix/WebApi.Explorations.ServiceBusRelayHostwww.moresimple.com