Thursday, February 5, 2015

Google Play Services Oauth2 Token Lookup via Cordova

Delegating to 3rd parties to manage your authorization is incredibly helpful when developing a new application. A benefit to users and developers alike, this task is made all the more helpful with the number of social networks providing Oauth2 APIs that we can use for our authorization. In this blog post I will address using the Google Play services on Android from a hybrid mobile Cordova application to retrieve an Oauth2 token that we can then use with Google’s Oauth2 REST API.

There are a number of blogs and how-tos on the web that show us how to use the Cordova InAppBrowser to trigger an Oauth2 token request. This approach works well, and indeed achieves the desired result of authenticating the user and retrieving an Oauth2 token. However the user experience is poor, requiring the user to enter their credentials. Why not leverage the already authenticated user logged into the mobile device? To achieve this we will have to make use of the Google Play services API.

Google Play services API

The Android documentation on Authorizing with Google for REST APIs is quite clear. We can use the Android API GoogleAuthUtil.getToken() method to retrieve an Oauth2 token for the logged-in user. The only missing link then is invoking the Android API from our javascript application.

A Cordova plugin

To close this gap, I created a Cordova plugin that invokes the GoogleAuthUtil API from a line of javascript, and returns the retrieved Oauth2 token to the javascript environment using a callback function. The Cordova Plugin Development Guide does a good job in describing how to author plugins. I recommend giving it a read if you are not familiar with developing Cordova plugins.

The only "gotcha" I had to deal with was the UserRecoverableAuthException that is thrown when first trying to retrieve the token. The above-mentioned Android documentation does a good job on describing how to catch the exception and retrieve appropriate permissions, but the Oauth2 token seems to get lost in the process. It turns out the token can be retrieved from an "Intent Extra" in the onActivityResult method of our plugin. Check out the plugin source if this is meaningful to you.

Consuming the plugin

The plugin I created is available on Github, and is installed using the command:

cordova plugin add https://github.com/bleathem/cordova-oauth-google-services.git

Remove this plugin with the command:

cordova plugin remove ca.bleathem.plugin.OauthGoogleServices

Invoke the plugin from your javascript:

window.cordova.plugins.oauth([scope], done, [err]);
  • scope optional: the scope for the Oath2 token request. Default: https://www.googleapis.com/auth/plus.me

  • done required: a success callback invoked the Oauth2 token as its single parameter

  • err optional: a failure callback invoked when there is an error retrieving the token

Example usage

In my Angular.js application I used a Promise API to retrieve the token:

var localLogin = function() {
      var deferred = $q.defer();
      $window.cordova.plugins.oauth.getToken('openid', function(token) {
        deferred.resolve(token);
      }, function(error) {
        deferred.reject(error);
      });
      return deferred.promise;
    }

I then posted the token to my backend where the token was verified and used to lookup/create a user. I set up a fallback mechanism to use the InAppBrowser approach to retrieve a Oauth2 token in cases where the Google Play services API was not present:

if ($window.cordova && $window.cordova.plugins && $window.cordova.plugins.oauth) {
      return localLogin().then(verifyToken, remoteLogin);
    }

The Final Word

This was the first Cordova plugin I created, and I must say I’m impressed at how easy it was to implement. I’ll definitely keep this tool close-at-hand when developing hybrid mobile applications.

Hopefully this Cordova plugin is useful to someone else; it certainly is easier to use than setting up the InAppBrowser solution!


Wednesday, January 28, 2015

Scalable splash screens with Cordova

Adding a splash screen to your mobile application is useful to provide users with feedback that their application is starting while performing any initialization tasks. In this blog post I will summarize how I created a scalable splash screen and how I configured my Cordova application to use it.

Drawing the splash screen

If you’re not an artist (as I am not!) then creating a graphical splash screen can be a somewhat daunting task. Finding a relevant image to use as a starting point can be a big help. I used the Creative Commons Search tool to find my initial image.

Edit the image using your favorite imaging editing software (I’m a fan of GIMP). Android pre-defines a set of sizes which you should target with your image:

  • xhdpi: 640 x 960

  • hdpi: 480 x 800

  • mdpi: 320 x 480

  • ldpi: 240 x 320

Similarly Apple has a set of predefined sizes, see the Apple HIG for details, but I’ll be addressing specifically Android with this post.

To keep things simple, I created a single square image to use for both portrait and landscape orientations:

Splashscreen

Making the splash screen scalable

Scaling the above image to fit the size and shape of a phone or tablet would distort the graphic and the text. To get around this we use the 9-patch file format to mark the areas of the image that can safely be stretched.

To convert the PNG file you created above into a 9-patch file, use the draw9patch application distributed with the android SDK. The basic steps of the conversion are as follows:

  1. Open you PNG file with the draw9patch application

  2. Drag with you mouse to mark the areas on the top and left margins where it is safe to stretch the image.

    • The right and bottom margins can be used to mark where content should be inserted into the image, and are useful when creating images for buttons. However this does not apply to splash screens, and you can safely ignore the right and bottom margins.

  3. Save your file with the *.9.png extension. The extension is critical, otherwise your splash screen image will not be interpreted as a 9-patch file, and the stretching will not be applied. I named mine splash.9.png.

Further details on using draw9patch can be found in the Android documentation.

Cordova build hooks

I like to structure my cordova projects to leave the platforms and plugins folders out of source control. I then use Cordova build hooks to install plugins and copy resources. For more information on build hooks, refer to this great post on using build hooks.

I place the above 9-patch image in the project folder config/android/res/screens and use the following build hook to copy this scm-controlled resource into the platforms folder:

030_resource_files.js
#!/usr/bin/env node
    
    //
    // This hook copies various resource files
    // from our version control system directories
    // into the appropriate platform specific location
    //
    
    // [{source: target}]
    var filestocopy = [
      ...
      , {
        "config/android/res/screens/splash.9.png":
        "platforms/android/res/drawable/splash.9.png"
      }
    ];
    
    
    var fs = require('fs');
    var path = require('path');
    
    var rootdir = process.argv[2];
    
    filestocopy.forEach(function(obj) {
        Object.keys(obj).forEach(function(key) {
            var val = obj[key];
            var srcfile = path.join(rootdir, key);
            var destfile = path.join(rootdir, val);
            console.log("copying "+srcfile+" to "+destfile);
            var destdir = path.dirname(destfile);
            if (fs.existsSync(srcfile) && fs.existsSync(destdir)) {
                fs.createReadStream(srcfile).pipe(
                   fs.createWriteStream(destfile));
            }
        });
    });

I’ve taken a one-size-fits-all approach with my splash screen. This works because I place my splash screen file in the platforms/android/res/drawable folder. If you want to create a different splash screen to accommodate each of the different screen sizes in either the portrait or landscape orientations, you can modify the above build hook to copy the appropriately sized files into each of the platforms/android/res/drawable-{port|land}-{ldpi|mdpi|hdpi|xhdpi}/ folders.

Cordova configuration

Finally we will install the cordova splashscreen plugin. This plugin allows us to manage the splash screen from our Cordova application.

We configure the default timeout and the name of our splash screen file in the config.xml of our project:

config.xml

    ...
    <preference name="SplashScreen" value="splash" />
    <preference name="SplashScreenDelay" value="10000" />
    ...
    

Using an appropriate listener in your Cordova application, dismiss the splash screen:

navigator.splashscreen.hide();

Conclusion

Getting the above pieces correctly lined up was surprisingly difficult. If the files are not named correctly, or placed in the wrong folder, everything falls apart. The Cordova documentation on the topic provides some help, but leaves out a lot of important details. This is apparent in the number of forum, stack overflow, and github issue threads on the subject. Hopefully this post helps someone shortcut the frustration of getting this working.