Using the Ionic Framework with Grunt and PhoneGap Build

posted in: Uncategorized | 11

I work at Conestoga College in Kitchener, Ontario where we serve over 10,000 full time students, and over 30,000 part-time students. Our students use a variety of devices while at the college in and between classes including many different models of phones and tablets. We are also limited in resources, much like a start up, but we also have the fun that is the politics of a fairly big organization.

When we first decided we wanted to tackle creating mobile apps, our first being an app for students to find available computer labs to work in, we had to take all of this into consideration. We decided that our best route would be to take our expertise in web technologies and go the route of a hybrid app. To save even more time and resources, we decide to go with Phone Gap Build (we also had a pre-existing licensing contract).

Version 1.0 App/Process

For version 1 of our app, we knew we had to get something out fast as some people had been waiting for a couple of years for this to happen. So, we decided to stick with what we knew: JQuery. And how did that app turn out?

Screenshot_2014-03-04-15-53-25

… well, it may have been ugly as hell, but it worked. Ish.

Our process for building it was to zip some files, but not all of them, go to Phone Gap Build, unlock the keys, upload the new zip file, wait for it to build, download the file, and then move it to the location we wanted the packaged apps to sit in.

Doesn’t sound hard, but it was terribly time consuming.

Version 2.0 App

For version 2 of our app, we went with a complete do-over, from scratch. We did some research and ended up finding the Ionic Framework, which was beautiful. Only problem was, we didn’t know Angular. It didn’t take long for us to pick it up, however, as it was pretty straight forward. Ionic fell into line as well and soon enough we had a beautiful iOS app, and a beautiful iOS app running on Android.

IMG_0308

I say iOS app running on Android because it really, really didn’t look like an Android app. It worked beautifully, but it didn’t LOOK right. So, we created a stylesheet that would override some styles and create a more Android-friendly look that we would just have to remember to put in before uploading it to the PGB service.

Screenshot_2014-05-22-13-37-56

Beautiful. Only problem, is the whole remembering to put in that style sheet first… not to mention all the other steps to get a packaged app. And just to make things more complicated, our iOS app and our Android app had different config files, another thing we would need to remember to change before packaging the app.

One other complication to mention is that we also support BlackBerry! Phone Gap Build doesn’t, so we had to set up a special environment for that, which means for BlackBerry, a totally separate skin, a totally separate config, and a totally separate process.

IMG_00000020

Also, some other notes

  • We wanted to keep track of the number of builds we did, a number that had to be manually incremented for each build
  • The “build number” would be used to name the build zip files
  • We wanted to be able to do Alpha, Beta, and Release builds, all of which would have some slightly different code and configuration
  • Blackberry uses a 4 digit version number, where the fourth digit is the build number
  • We eventually want to support Windows Phone
  • Each platform has ever so slightly different markup

Grunt to the Rescue

So, we have ALL these steps, differences, and challenges, so what do we do? We use a build tool to automate.

We have a mixture of plugins and custom coded tasks to accomplish our needs. Our grunt command looks like this:

grunt build:[platform]:[release] [–package?]

… where platform can be ios, android, or blackberry, release can be alpha, beta, or release, and package is an optional parameter that if not set will cause the build tool to just build the zip file but not package it into an app. Example usage:

grunt build:ios:beta –package

What will this command do?

  1. Bump the “build number” which is stored in our package.json file
  2. Inject the the version and build number into our package.json file
  3. Inject the platform specific configuration into the config.xml file
  4. Inject the platform specific styles into the index.html file
  5. Rename the “name” in the config.xml to alpha or beta, if needed
  6. Replace a service URL to point at either the dev version or the live version of a web service
  7. Replace the default parent file (parent file has the base markup for the app) with the platform specific one
  8. Create a zip file in a /builds folder that contains our updated files including index.html, config.xml, all of our images, css files, js files, templates, etc. and is named [app name]-[version]-b[build number].zip (example: Lab Search-2.0.0-b182.zip)
  9. Connect to the Phone Gap Build API
  10. Unlock our apps keys
  11. Upload our zip file to the service
  12. Wait for the file to be packaged into an app
  13. Download the file and add it to a “packaged app” folder, under a platform specific sub-folder. (ex: packaged app/ios/Lab-Search-beta-b281.ipa)

The beautiful part? This all happens in about 15-20 seconds. How long would this take manually? You can imagine.

Another interesting tidbit is that if I had said BlackBerry instead of iOS, there would be a few extra steps, and instead of using the PGB service, it will package using webworks locally, but still save it to the same location.

So, we are now at a point where I type a simple command, a nice laundry list of commands that would easily take a couple of minutes and be different for every platform is executed in 15-20 seconds and I don’t have to think about all of these steps every time. I type a command, I get a packaged app. Period.

Plugins that we use

Custom Tasks

BumpBuild

This task grabs a “build” property from the package.json file and increments it, before saving it back to the file.

grunt.registerTask('BumpBuild', function() {		
	var file = grunt.config('pkgFile') || 'package.json';
	var pkg = grunt.file.readJSON(file);
	var newBuild = parseInt(pkg.build) + 1;
	
	grunt.log.write('Building '+pkg.name+' '+pkg.version+' build '+pkg.build).ok();
	
	pkg.build = newBuild;
	grunt.file.write(file, JSON.stringify(pkg, null, '  ') + 'n');
});

Build

This task, depending on the platform and release passed in, calls other tasks.

grunt.registerTask('build', function(os,release){
	var usage = "Usage: grunt build:[os]:[release] [--package]?";
	if(os == null){
		grunt.log.error(usage);
		grunt.fail.fatal("Operating System not provided");
	}
	if(release == null){
		grunt.log.error(usage);
		grunt.fail.fatal("Release not provided");
	}
	
	if(os != "blackberry" && os != "android" && os != "ios" && os != "winphone"){
		grunt.fail.fatal("Operating System not valid");
	}
	
	if(release != "alpha" && release != "beta" && release != "release"){
		grunt.fail.fatal("Release not valid");
	}
	
	grunt.log.write("Building for "+os+" "+release+"...").ok();
	grunt.config.set("pkg.platform",os);
	
	switch(os){
		case "blackberry":				
			grunt.task.run(['BumpBuild','string-replace:config','string-replace:blackberry','string-replace:'+release,'parentfilerewrite:blackberry','compress']);
			break;
		case "android":
			grunt.task.run(['BumpBuild','string-replace:config','string-replace:android','string-replace:'+release,'parentfilerewrite:android','compress']);
			break;
		case "ios":
			grunt.task.run(['BumpBuild','string-replace:config','string-replace:ios','string-replace:'+release,'parentfilerewrite:ios','compress']);
			break;
		case "winphone":
			grunt.task.run(['BumpBuild','string-replace:config','string-replace:winphone','string-replace:'+release,'parentfilerewrite:winphone','compress']);
			break;
	}
	
	//Package it up using webworks or Phonegap Build
	if(grunt.option('package')){
		if(os == "blackberry"){
			grunt.log.write("Packaging using Webworks...").ok();
			grunt.task.run(['exec:bbwp']);
		} else {
			grunt.log.write("Packaging using Phonegap Build...").ok();
			grunt.task.run(['phonegap-build:'+release]);
		}
	} else {
		grunt.log.write("Not Packaging").ok();
	}
});

Conclusion

While I have not shown the specifics of the configuration of Grunt in this article, I have shown what is possible by using a mixture of a good frameworks and a build tool to automate many of the time consuming tasks that come with creating a cross platform mobile app.

Feel free to leave a comment if you have any questions about anything in the process.

My name is Andrew McGivery. I currently work full time as an application developer at Manulife Financial in Canada. My current passion is building and leading highly engaged teams where employee happiness, learning, and growth is a priority.

11 Responses

  1. cfjedimaster

    Great post! Query – why not consider building locally? Would be even faster using the local CLI.

    Finally – ok – this is OT and not really important, but it is a pet peeve of mine. 😉 Did users *honestly* have issues with the iOS theme on Android? I mean – I look at the two screenshots – one is white, one is black. The hamburger icon moved. Seriously – that made the app more usable? I just can’t believe that! (But am willing to be proven otherwise. 🙂

    • Hey Raymond,

      There isn’t really a show stopping reason that we don’t build locally. It’s partially convenience, partially less setup, partially because I don’t remember anymore. 🙂

      As for the Android skin, I agree that with what I posted, there is almost no difference, but it is actually a work in progress. The first step was to do a few minor modifications, but soon we will make it look and feel even more native to Android by implementing the Android action bar, and having the menu only show up on the home screen. Eventually we want to get to the point where our apps look and feel native on each platform, and the average user won’t ever be able to tell the difference between our app and a native app.

  2. cfjedimaster

    Btw – one reason to consider building locally – when you do, you can automatically handle different CSS for different platforms using the Merges feature, something PGB doesn’t yet support.

  3. This is excellent. Thanks for putting this out there. I suppose it’s too much to ask the Ionic guys to include this in their guides but they should do really since they are geared to people using PhoneGap so PGB automation fits with their ethos. And as the Ionic CLI gives me errors but I can build with PGB this looks like a great alternative.

    • cfjedimaster

      @Sub_Effect, have you reported those CLI bugs?
      As it stands, I don’t think this article makes sense for the guides, it is too specific, but they do have a video podcast. It should be highlighted there imo.

  4. Dmytro Shchurov

    One note. AppStore approval policy does not allow any alphas or betas. I think it makes sense. Thus [alpha|beta|release] dimension is redundant IMHO.

    • Hey Dmytro,

      Not exactly true.

      When I wrote this, that was only partially true. You couldn’t submit anything but release to Apple, BUT you COULD test on devices without going through the App Store, hence the need for Alpha/Beta/Release using the proper mobile provisioning profile.

      Apple last year purchased TestFlight which now allows proper Alpha AND Beta testing. Go check it out.

      Google Play allows for Release and Beta builds which are super useful. Alpha builds would be builds you side load to internal devices without going through the store.

      For BlackBerry, they only accept release builds… at the moment. They are working on releasing BlackBerry Beta Zone to the publish to allow for better beta testing, and again Alpha testing would be through side loading to internal devices.

  5. […] Using the Ionic Framework with Grunt and PhoneGap Build […]

Leave a Reply