[IoT] Aquarium monitor; mobile app, Cordova style
Finally! Post #6 in my blog series on building an aquarium monitor solution using Azure! In my last post we created a Web API project which provides an API for any application to use. In this post, we’ll take a look at creating an Apache Cordova application for use on mobile phones. This app will consume the API and voila, we’ll then have our readings displayed in a smartphone app. This will complete the journey from reading data, sending it to the cloud and then consuming it on a mobile phone (or any other app for that matter). In a next post, I’ll describe how to build out this scenario and for instance add notifications to alert you when certain readings appear to be off.
Apache Cordova mobile apps
Ok. So let’s start with explaining what a Cordova app is. Apache Cordova is a technique which allows us to build platform independent apps using HTML and JavaScript. A Cordova app can be deployed to Android, iOS and Windows phones. It uses platform specific libraries which act as proxy between the app and platform capabilities like the camera, notifications, contacts, etc.
Since this blog is about Azure and IoT, a Cordova app is just the means of consuming the API. Therefore I won’t go into too much detail about building a Cordova app, that too is a topic on it’s own. Check out the Getting Started with Visual Studio Tools for Apache Cordova page for more information and lots of samples on the subject.
So step 1: check out the tutorials mentioned above and create your Cordova app! You can either deploy it to your phone directly (after enabling the developer capabilities) or use one of the simulators. Personally I like deploying to the phone best, as the simulators can get a bit laggy.
Angular & Material Design
Ok so once you’ve got your Cordova app set-up (hello world style), you’ll need to think about the architecture of that app. Luckily, since we’re just using HTML and JavaScript here, there’s a lot of stuff you can re-use. That “stuff” includes one of the most commonly used frameworks out there: AngularJS.
Also, you’ll need to think about the UI of the app itself. A Cordova app will give you a blank canvas to work with. Of course you can choose to fill that in all the way yourself but that would be a lot of work. Also, some nifty designer folks have already thought out some cool guidelines and frameworks you can use to more easily style your apps. I chose to go with Material Design, which nicely fits the rest of the OS and apps I’ve got running on my Android powered smartphone.
Time for some more links. More information on Material Design is found here. Then there is Angular Material, which provides directives for Angular to easily use Material components inside of your Angular apps. More information on Angular Material @ https://material.angularjs.org/. If you choose to go the same way as I did; make sure to check out this seed on GitHub, which provides an excellent starting point having a Cordova app already set-up for AngularJS and Angular Material.
Azure Mobile Apps
The next component we’ll add will take care of authenticating and chatting with our Web API endpoint. Remember, this is secured using Azure AD and we’ll need to take care of logging in the user and making sure authentication tokens are sent with each call to the API. You can do all of this yourself but you might have noticed I’m lazy so once again, I’ll be using a ready made component to do so.
This component is what is called “Azure mobile apps” and it’s focused on providing and consuming Azure on mobile platforms. So on the one side, there are server side components you can use to extend your Web API project and on the other side there are client side libraries which make it easy to connect with these API’s.
Let’s start with step 1: configuring our Web API project with Azure Mobile Apps. For this we’ll add another partial Startup class with the following routine which should be called from the main Startup class:
public void ConfigureWebApi(IAppBuilder app) { HttpConfiguration config = Startup.HttpConfiguration; config.MapHttpAttributeRoutes(); config.EnableSystemDiagnosticsTracing(); new MobileAppConfiguration() .UseDefaultConfiguration() .ApplyTo(config); app.UseWebApi(config); }
You’ll need the Microsoft.Azure.Mobile.Server package to do this, which comes with a few more things:
- Microsoft.Azure.Mobile.Server.Authentication
- Microsoft.Azure.Mobile.Server.CrossDomain
- Microsoft.Azure.Mobile.Server.Home
- Microsoft.Azure.Mobile.Server.Entity
- Microsoft.Azure.Mobile.Server.Notifications
More information about setting up your back-end and Cordova application is found here. Note that the article details setting up a brand new Mobile Apps API back-end. This is not required, you can also add the mobile apps components to an existing Web API back-end as described above. Azure won’t list your app as a mobile API back-end (the icon is different) but this is not a problem.
Client components
So setting up the server components was easy, now over to the client components. To add these, you’ll need to add a new plugin to your Cordova app. Open the config.xml file, select the Plugins pane and click “Custom”. Here you can add custom plugins based on git repositories. Add the following one:
https://github.com/Azure/azure-mobile-apps-cordova-client
You’ve now added pretty much what you need to connect. But you probably want to have a centralized place to do API calls and handle logins. For that purpose, I recommend creating an Angular service which you can easily inject into your controllers. Here is an example of how I created such a service:
angular.module("ngapp").service("mobileAppsClient", function (shared, $log, $q, jwtHelper) { var mobileServiceClient = new WindowsAzure.MobileServiceClient('https://[YOURWEBAPI].azurewebsites.net'); var loggingIn = false; function loggedIn() { // If token doesn't exist, we aren't authenticated if (mobileServiceClient.currentUser === null) return false; // Verify expiry time var token = mobileServiceClient.currentUser.mobileServiceAuthenticationToken; var decoded = jwtHelper.decodeToken(token); if (typeof decoded.exp !== 'undefined') { // exp is a UNIX Timestamp var ts = Math.round((new Date()).getTime() / 1000); if (ts > decoded.exp) return false; } console.log("Found a valid authentication token."); // If there isn't an expiry or we haven't expired, we are authenticated return true; } var login = function (force) { var deferred = $q.defer(); // set the default value of the force parameter to false force = typeof force !== 'undefined' ? force : false; // Login to the service if ((loggingIn == false && !loggedIn()) || force) { loggingIn = true; console.log("Logging in MobileServiceClient with aad authentication."); mobileServiceClient.login('aad').then(function () { console.log("Successful login with Azure mobile app."); loggingIn = false; deferred.resolve(); }, function (err) { console.log("Could not login into mobile services."); loggingIn = false; deferred.reject(err); }); } else { console.log("Login resolving because already logged or logging in."); deferred.resolve(); } return deferred.promise; }; var invokeApi = function (apiName, options, callback) { login().then(function () { mobileServiceClient.invokeApi(apiName, options, callback); }, function () { console.log("Cannot invoke API because login failed."); }); } return { login: login, invokeApi: invokeApi }; });
As you can see, it’s pretty simple. The WindowsAzure.MobileServiceClient does the heavy lifting, the only thing you need to put in is the URL to your Web API hosted in Azure.
By the way, it’s good to know the difference between Azure Mobile Services and Azure Mobile Apps. The first one is regarded legacy and has been replaced by the second one, so you really should only be using the Apps variant. But as one might expect, not all code has been refactored yet and so the client library is still named MobileServiceClient, which in my opinion should be MobileAppsClient. I filed a GitHub issue for this, but until that time just know the difference between the two.
By the way #2: the jwtHelper injected into the service is a helper service for JWT authentication tokens. I use it to check the validity of the current token which is an indication whether the user is logged in or not. You can install that library using Bower:
bower install angular-jwt
Calling an API
Ok, cool! Let’s start calling API’s! We’ll inject our newly made service into an Angular controller and use the invokeApi method to call the API endpoint:
angular.module("ngapp").controller("LiveController", function (shared, mobileAppsClient, $scope, $timeout) { // get the latest available data record to show mobileAppsClient.invokeApi("live", { body: null, method: "post" }, function(error, success) { if (error) { // do nothing console.log ("Error calling /live: " + error); } else { processData(success.result); // process the last known data for display } }); }
Awesome how easy this is, right!? With the right plumbing, calling an API endpoint is now very simple. And mind you, the above code will:
- Check whether our user is authenticated.
- When this is not the case, open a pop-up requesting the user to log in using valid AD credentials.
- AD will process authentication, including two-factor sign-on when the user has this configured (I have it enabled for my Microsoft Account).
- Handle the authentication result and store a cookie with an authentication token for future use.
- Refresh this token each time a new call is done.
- Pass along the token in the Authorization header of each API call.
And it does this without bothering you with it, exactly what you want. Now the success.result variable will contain the object returned by the Web API endpoint.
Help! It doesn’t work!
I’ve only shared bits and pieces of the complete solution at this point. I’m still looking into open sourcing all of the code but to be honest it needs some major refactoring before I’m comfortable with that. So it could well be that you’ve copy/pasted stuff but it’s not working. Here are some pointers that might help:
- Test your API endpoint from a browser first. You can manually call the endpoint to see whether data is returned in the first place. Chrome has some cool extensions available to test API’s, for instance to fire POST calls instead of the default GET. I personally like the Advanced REST client found here.
- Use chrome://inspect instead of Visual Studio to debug an app running on your phone. I didn’t know this trick before, it’s quite awesome. Whenever a debug-enabled phone is attached to your PC (and it’s not in a debug session already) you can use chrome://inspect to open up a chrome window which will have the exact same app. This only works for Cordova apps since those are HTML / JavaScript based. You can use the default Chrome debug tools to, for instance, inspect the network traffic going over the line. Check the correct URL is called, check the Authorization header is set, etc. That especially I found very useful to find out where API calls went wrong and why.
- KISS! I cannot stress this enough. Keep It Simple Stupid! Don’t try to go full blown upon your first deployment attempt. Do a single API call in a simple hello-world style example first. Once you have that working, start building it out towards your end goal. If you start with too much bells and whistles, it’ll become increasingly difficult to find the origin of issues.
The result
Here’s a screenshot of what I have at the moment:
It’s a mobile app which displays two temperature readings and a pH reading. That last one is dramatically off by the way but that’s because I need better hardware for more reliable readings. In my next post, I’ll be detailing how you can add notifications and live updates using SignalR to your app. Stay tuned!
March 14, 2016 at 10:03 am |
[…] [IoT] Aquarium monitor; mobile app, Cordova style […]