Faster hybrid development with Yeoman, Ionic and Cordova

Aug, 2, 2014 • Bruno De Simpelaere

Categories: Angular, Cordova, Html5, Ionic, Phonegap

While searching for a blogpost to set up a development environment with Ionic & Grunt I ran into this blogpost “Quickly start an app using Yeoman, Ionic, AngularJS and PhoneGap” , my interest was woken and I tried it myself because it seemed like a proper way to set up such an environment.

Unfortunately this didn’t work for me. I couldn’t initialize a PhoneGap (or Cordova) project in a non-empty folder. So I ended up with a slightly different approach to do the same thing and extended it by pulling the Android & iOS build commands into grunt.

##Start your engines I’ve only tested the following on Mac OS X. For those on windows or linux, you might need some different syntax.

If you already installed Yeoman and Cordova, you can skip this section and go directly to “Release the clutch”. For the rest of us mortals I’ll give a quick overview of the “engines” we’re going to use.

###First Gear: node.js Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.[node.js website]

To install node.js you could use homebrew or just download the latest package from the node.js website.

Intermezzo: Installing via homebrew

After you’ve installed node you can use the node command in your terminal. To be sure everything is installed you can try it yourself.

$ node -v   //v0.10.29
$ npm -v    //1.4.9

Second Gear: Yeoman

Yeoman helps you kickstart new projects, prescribing best practices and tools to help you stay productive. To do so, Yeoman provides a generator ecosystem. A generator is basically a plugin that can be ran with the yocommand to scaffold complete projects or useful parts. [yeoman website]

Yeoman depends on Grunt which is a javascript taskrunner & bower which is a package manager, with the combination of these tools you can really save a lot of time on scaffolding, building & deploying your website.

$ sudo npm install -g grunt
$ sudo npm install -g bower
$ sudo npm install -g yo
$ sudo npm install -g generator-angular

You might have noticed the -g (or –global). This is because we want to install these node modules globally and not only for this project.

Again you can check if everything is installed correctly with the -v (–version) option.

$ grunt -v // 0.4
$ bower -v //1.3.7
$ yo -v //1.2.0

Third Gear: Ionic framework

Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly interactive apps. Built with Sass and optimized for AngularJS.

You can install a CLI-tool for ionic too, but for the moment I’m not going to use it because it’s mainly a wrapper for PhoneGap/Cordova commands. It also uses Gulp, which is yet an other javascript task runner. Personally I prefer Grunt, which I also use in other non-mobile projects.

Fourth Gear: PhoneGap / Cordova

PhoneGap & Cordova are almost the same, PhoneGap is Cordova but owned by Adobe. Cordova is the open source version of almost the same thing.

To install Cordova we’ll repeat the same thing again

sudo npm install -g cordova

And once again you can check if everything is installed correctly with the -v (–version) option.

$ cordova -v // 3.5.0-0.2.6

TL;DR: You can install all these in one command npm install -g cordova yo generator-angular bower grunt

Release the clutch

We’ve now installed all the needed “engines” and fitted all the parts. So let’s start the actual scaffolding of our application. This is pretty straightforward if you respect the order of scaffolding the different layers. This is due to the fact cordova create . "com.example.app" "demo" doesn’t want to sit in a non-empty folder. (F.Y.I. I had the same problem with the Phonegap variant).

Finding a Campingspot

Before we continue, it seems like a good idea to tell you what we’re trying to achieve.

If you generate a Yeoman angular project, you’ll have a “development” folder, and a “distribution” folder. The first one is used for development and the other is the “clean and lean” version with minified scripts, optimized images, etc. to deploy online.

The ‘app/’ directory is the development version, and the ‘dist/’ directory is the distribution version.

If we use Cordova (or PhoneGap), the ‘native’ applications are generated from the ‘www’ directory, which will end up in the platforms folder.

We can’t change the fact that Cordova needs the ‘www/’ directory for deploying applications, but we can change the name of the Yeoman distribution folder.

Hence we’ll change the ‘distribution’ folder name to ‘www’ instead of ‘dist’. (don’t worry to much about this right now, we’ll handle it later on).

Let’s start by creating a folder for our application and change our working directory to it.

$ mkdir ionic-demo
$ cd ionic-demo

After we’ve created our application folder and moved in to it, we can scaffold a Cordova project inside of it.

$ cordova create . "com.example.app" "ionic-demo"

Now that the foundation for a mobile application is laid out, we can add a new layer on top of it. This would be the AngularJS layer. We can simply add this by running the Yo Angular-generator

$ yo angular

Yeoman will ask a few questions, the only thing you need to remember is say ‘Yes’ to using Sass and ‘no’ to using bootstrap. After this Yeoman will ask us if we want to install some additional angular modules. You can select the ones you need (or think you’re going to need).

Now we’ve scaffolded an Angular-Cordova (or Cordova-Angular) project.

Riding in a straight line.

In order to make our application build to the ‘www’ folder instead of the ‘dist’ folder. We’ll change the destination of the appConfig.dist property in our Gruntfile.js.

/Gruntfile.js

    module.exports = function (grunt) {
        [...]
      // Configurable paths for the application
      var appConfig = {
        app: require('./bower.json').appPath || 'app',
        dist: 'www' //changed  from dist: 'dist'
      };
      [...]
    }

Make sure we keep in our lane

We can make some changes in our config.xml file to optimize our application.

/config.xml

  • Add the DisallowOverscroll preference to prevent the application from overscrolling <preference name="DisallowOverscroll" value="true"/>
  • Add the UIWebViewBounce preference to prevent the bouncing animation in your application <preference name="UIWebViewBounce" value="false" />

We don’t want to slow down

We need to add a <script> tag for our Cordova script, which is needed by our application to include and use the Cordova magic.

/app/index.html

    <script src="cordova.js"></script>

We could also remove the conditional tags for older browsers because this isn’t need for mobile applications (unless you’ll find a IE6 phone or something like that)

Shims

    <!--[if lt IE 9]>
    <script src="bower_components/es5-shim/es5-shim.js"></script>
    <script src="bower_components/json3/lib/json3.min.js"></script>
    <![endif]—>

And browserhappy

    <!--[if lt IE 7]>
    <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
    <![endif]-->

Let’s ignore the flies on the windshield

Because we want to create a Git repository of our application, we’ll try to keep it as clean as possible for others to find their way in our “frankenstein” scaffold.

echo 'www/' >> .gitignore (Add directory www to .gitignore)
echo 'npm-debug.log' >> .gitignore (Add file npm-debug.log to .gitignore)
echo '.*' >> .gitignore (Add hidden files to .gitignore but you should force add bower and co configuration files to git)
echo 'platforms/' >> .gitignore (Add directory platformsto .gitignore)
echo 'plugins/' >> .gitignore (Add directory plugins to.gitignore)

We don’t need no dead weight

We’ve already removed the references to our old-browser-shims. But we don’t want them to pop up again. So we can remove the following lines from our bower.json file.

/bower.json

    "json3": "~3.3.1",
    "es5-shim": "~3.1.0",

Finally something with Ionic in it.

Now our project is cleaned up, we can add something extra to it. And not a small extra but a full blown HTML5 mobile application framework: Ionic!

We need to add following lines to our bower.json file. These will add angular-ui-router (needed by Ionic) and Ionic itself.

I’ve tried to use "ionic":"latest" but this keeps installing the 0.9 version, and to be honest the 1.0 beta version works a bit better.

/bower.json

    "angular-ui-router":"latest",
    "ionic": "1.0.0-beta.9"

After we’ve added above lines to our bower.json file, we’ll need to run the following command to actually add these dependencies to our application.

$ bower install
$ grunt wiredep

Let’s add the blinking engine light

This is one of the reasons why we wanted to combine Yeoman scaffolding, Cordova and Ionic in the first place. To have an easy testing environment, to enable our testing environment we need to update the dependencies in the test/karma.conf.js file too.

This kinda depends on the modules you’ve chosen when you ran yo angular but basically you’ll just need to depend the same javascript files as you depended in your index.html plus the angular-mocks.js file which is used for the testrunner.

/test/karma.conf.js

e.g.

      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'bower_components/angular-resource/angular-resource.js',
      'bower_components/angular-sanitize/angular-sanitize.js',
      'bower_components/angular-cookies/angular-cookies.js',
      'bower_components/angular-animate/angular-animate.js',
      'bower_components/angular-route/angular-route.js',
      'bower_components/angular-ui-router/release/angular-ui-router.js',
      'bower_components/ionic/release/js/ionic.js',
      'bower_components/ionic/release/js/ionic-angular.js',

To make sure everything is OK, I would advise to run grunt test until it gives no significant errors anymore.

$ grunt Test

Almost at the coffeestop!

“Can somebody please tell me why we’re doing this in the first place? “

Because we want to set up an automatic build for our applications so we don’t need to type commands in our terminal all day.

What if I told you, there’s a Grunt Cordova module too and Yeoman uses Grunt, so we can use it? And what if I told you that you can run the whole process with just one command?

“Right, that would be awesome.”

Let’s build a rocket!

To install the Grunt Cordova Command line interface, we need to install and save it to our package.json file (this can be done by adding –save-dev to it)

npm install grunt-cordovacli —-save-dev

By adding it to our package.json file, other developers will know they’ll need this file, and also (this setup of) grunt will know that it needs to load it.

To add a task to our Gruntfile, we need to define an object into grunt.initConfig().

    cordovacli: {
          options: {
            path: '/'
          },
          add_platforms: {
            options: {
              command: 'platform',
              action: 'add',
              platforms: ['ios', 'android']
            }
          },
          add_plugins: {
            options: {
              command: 'plugin',
              action: 'add',
              plugins: [
                'battery-status',
                'camera',
                'console',
                'contacts',
                'device',
                'device-motion',
                'device-orientation',
                'dialogs',
                'file',
                'file-transfer',
                'geolocation',
                'globalization',
                'inappbrowser',
                'media',
                'media-capture',
                'network-information',
                'splashscreen',
                'vibration'
              ]
            }
          },
          build_ios: {
            options: {
              command: 'build',
              platform: 'ios'
            }
          },
          build_android: {
            options: {
              command: 'build',
              platform: 'android'
            }
          },
          run_ios: {
            options: {
              command: 'run',
              platform: 'ios'
            }
          },
          run_android: {
            options: {
              command: 'run',
              platform: 'android'
            }
          }
        },

Cordovacli.options.path is set to ‘/’ because we use the rootfolder as base for our Cordova application.

Cordovacli.add_platforms can be used to add platforms to our project (in this case iOS & Android)

Cordovacli.add_plugins can be used to add additional plugings to our project.

Cordovacli.build_xxx is the task that actually builds the application for iOS or Android.

Cordovacli.run_xxx is the task that can be run to emulate the application in an emulator or on a device.

We can now use these tasks by running

grunt cordovacli:build:ios

But we want more and actually need more now that we’ve come this far. As I mentioned before, Cordova uses the ‘www’ folder for building the ‘native’ applications. But if you run grunt cordovacli:build:ios without grunt build your latest changes sometimes aren’t included.

So to be sure this doesn’t happen, we’ll transform the grunt build command to a grunt build, grunt build:ios and a grunt build:android command, like shown below.

Now, when we run grunt build still only ‘www’ will be updated, but when we would run grunt build:ios or grunt build:android ‘www’ gets updated and Cordova will build a “native” iOS or “native” android app, from your latest changes.

    grunt.registerTask('build', 'building your application', function (target){
        if( target === ''){
          grunt.task.run([
            'clean:dist',
            'wiredep',
            'useminPrepare',
            'concurrent:dist',
            'autoprefixer',
            'concat',
            'ngmin',
            'copy:dist',
            'cdnify',
            'cssmin',
            'uglify',
            'filerev',
            'usemin',
            'htmlmin'
          ]);
        }
        if( target === 'ios'){
          grunt.task.run([
            'clean:dist',
            'wiredep',
            'useminPrepare',
            'concurrent:dist',
            'autoprefixer',
            'concat',
            'ngmin',
            'copy:dist',
            'cdnify',
            'cssmin',
            'uglify',
            'filerev',
            'usemin',
            'htmlmin',
            'cordovacli:build_ios'
          ]);
        }
        if( target === 'android'){
          grunt.task.run([
            'clean:dist',
            'wiredep',
            'useminPrepare',
            'concurrent:dist',
            'autoprefixer',
            'concat',
            'ngmin',
            'copy:dist',
            'cdnify',
            'cssmin',
            'uglify',
            'filerev',
            'usemin',
            'htmlmin',
            'cordovacli:build_android'
          ]);
        }
  });

In addition to our grunt serve task, we can register a grunt run:ios and grunt run:android task to emulate what we’ve build on an iOS emulator or Android emulator or device.

      grunt.registerTask('run', 'run your application', function (target){
        if( target === 'ios'){
          grunt.task.run([
            'cordovacli:run_ios'
          ]);
        }
        if( target === 'android'){
          grunt.task.run([
            'cordovacli:run_android'
          ]);
        }
      });

Continue reading

View all articles

Objective-C dependency injection in 4 easy steps

In this article we’ll show you how to easily apply dependency injection (DI) in your code with Reliant, our open-source DI framework. In a few easy steps you’ll learn how to set up a very simple ‘Hello World’ DI scenario and along the way we’ll explain why DI is actu[...]

Top 5 libraries every iOS developer should know about

Libraries, we all know them and we all use them. They make our life as developers easier and let us win days of precious dev-time. In this post I would like to guide you through my personal top 5 Objective-C libraries. Let’s get started with number one: [...]