A Maintainable Cordova Boilerplate

Cordova is great but when working as part of a team or when revisiting projects after a period of downtime it can often be a bit of a struggle to get back up and running. The globally installed version of the Cordova CLI may be different, or there may simply be merge conflicts with files in the platforms folders.

With a bit of thought (lots of getting it wrong!) and taking advantage of Grunt, we can make this a lot easier on ourselves.

The below assumes you already have Grunt setup within your Cordova project. If you want to jump straight into the code, I’ve created an example project on Github.

Lock the Cordova version used for the project

It’s important to be able to ensure that a specific version of Cordova is being used across the project team. When installing Cordova via NPM it is recommended that you do so globally, which makes sense but does make it hard to keep different projects on different versions.

To counter act this we’ve found it useful to pin the Cordova version used for each project and then upgrade on a case by case basis. To do this, we use grunt-cordovacli.

First we’ll add it to our package JSON:

npm install grunt-cordovacli --save-dev

Next we’ll install the desired version of Cordova to this project, e.g.:

npm install cordova@5.1.1 --save-dev

The great thing about grunt-cordovacli is that it provides a way to access all the normal CLI commands but using the project specific version of the Cordova CLI package. I’d recommend reading the docs docs but here is an example gruntconfig:

cordovacli {
    options: {
        path: '.',
        cli: 'cordova'
    },

    create: {
        options: {
            command: 'create',
            id: 'com.companyname.appid',
            name: 'App Name'
        }
    },

    addPlatforms: {
        options: {
            command: 'platform',
            action: 'add',
            platforms: ['ios', 'android']
        }
    },

    removePlatforms: {
        options: {
            command: 'platform',
            action: 'remove',
            platforms: ['ios', 'android']
        }
    },

    updatePlatforms: {
        options: {
            command: 'platform',
            action: 'update',
            platforms: ['ios', 'android']
        }
    },

    prepare: {
        options: {
            command: ['prepare'],
            platforms: ['ios', 'android']
        }
    },

    build: {
        options: {
            command: 'build',
            platforms: ['ios', 'android']
        }
    },

    runAndroid: {
        options: {
            command: 'run',
            platforms: ['android']
        }
    },

    runIos: {
        options: {
            command: 'run',
            platforms: ['ios']
        }
    }
}

With the above config you could then build your project using:

grunt cordovacli:build

By running all commands through grunt-cordovacli in this way, we can be sure that we’re always using the same version of Cordova. When the time comes to update the Cordova version, we just need to update our package.json to include the new version of Cordova (locally) then run:

npm install
grunt cordovacli:updatePlatforms

Make the project more Git friendly

The bulk of any Cordova app can be found in the www folder, this is then copied into the various platform folders that you’re targeting. We don’t really want to keep track of these duplicate files in our git repo, nor do we really care about the platform specific files (e.g. Xcode Project, Android Manifest etc).

Our aim should be to be able to rebuild these platform files whenever we need. This might seem like overkill but from experience deleting and rebuilding an XCode project has been the easiest way to fix a number of issues we’ve encountered.

Remove non app files from the repo

We’ll start by removing the contents of platforms and plugins from our git repo. Add the following to .gitignore

platforms/*
!platforms/.gitkeep
plugins/*
!plugins/.gitkeep

We still need these folders in our repo as that is how the Cordova CLI tests that a project has been initialized, so we must add a .gitkeep file to both directories.

Ensure we can rebuild on demand

We’ve now removed any non app specific code from the repo which means we need a way to rebuild the platforms when ever we need. To do this we add a new Grunt task that essentially removes & then adds all the platforms we care about:

grunt.registerTask('setup', ['cordovacli:removePlatforms', 'cordovacli:addPlatforms']);

When run, this will remove any platforms (if they exist) and then add all the platforms you’ve setup in the cordovacli config above. When adding platforms, any plugins specified in your config.xml file (supported in Cordova 5+) will automatically be installed too.

The above command works, but leaves us with a bit of an issue. When removing/adding platforms, we’ve also thrown away any custom icons and splashscreen images associated with the project.

This is easily fixed by keeping your icons and splash screen assets elsewhere in your project and using grunt-contrib-copy to copy them to the correct platform folders as part of the project rebuild. First, install grunt-contrib-copy:

npm install grunt-contrib-copy --save-dev

Then setup tasks to copy assets into the right locations:

copy: {
    resourcesAndroid: {
        files: [
            {expand: true, cwd: 'resources/android/videos', src: ['**'], dest: 'platforms/android/res/raw'},
            {expand: true, cwd: 'resources/android/icons-and-splash', src: ['**'], dest: 'platforms/android/res'},
        ]
    },
    resourcesIos: {
        files: [
            {expand: true, cwd: 'resources/ios/icons', src: ['**'], dest: 'platforms/ios/[Name of Project]/Resources/icons'},
            {expand: true, cwd: 'resources/ios/splash', src: ['**'], dest: 'platforms/ios/[Name of Project]/Resources/splash'}
        ]
    }
}

Then finally, add the appropriate items to the setup task:

grunt.registerTask('setup', ['cordovacli:removePlatforms', 'cordovacli:addPlatforms', 'copy:resourcesAndroid', 'copy:resourcesIos']);

Use config.xml to be more prescriptive

The heart of a Cordova projects configuration is done in the config.xml file and there are lots of helpful parameters that can be set that will make developing within a team more manageable.

App version and build number

The widget element comes pre-populated with the version attribute but its also useful to be able to keep track of build numbers. This is especially useful when working towards a specific version release but a series of alpha/beta testing is required. In this scenario the version attribute would stay constant but you’d want the build number to increase. This can be achieved with the following attributes:

If you want to access the build number in your Cordova app, checkout cordova-plugin-appversion

Specify the target OS & device type

For an Android project you can set the SDK version to build against, and the minimum version to support:

<platform name="android">
    <preference name="android-minSdkVersion" value="16" />
    <preference name="android-targetSdkVersion" value="19" />
</platform>

On iOS you can set the minimum SDK version to support & also the type of devices to target (handset, tablet, or universal):

<platform name="ios">
    <preference name="target-device" value="handset" />
    <preference name="deployment-target" value="7.0" />
</platform>

Going further

Whilst most of what is needed can be handled in config.xml, there are occasions that we need to go a little further. This might be a change to a plist file in XCode or manipulate settings in AndroidManifest.xml. In these situations I’ve found grunt-xmlstoke and grunt-plistbuddy useful.

Building for Release

Ignoring issues around certificates, building a release build for iOS is pretty straight forward within XCode, so isn’t part of our Grunt setup.

Building release builds for Android is possible from the command line and so its a good idea to build it into our Grunt setup so that we don’t have to remember the specifics of how to do it each time.

First of all, add the following task to the cordovacli config:

releaseAndroid: {
    options: {
        command: 'build',
        platforms: ['android'],
        args: ['--', '--release', '--gradleArg=-PcdvReleaseSigningPropertiesFile=../../android-release-signing.properties']
    }
}

Its pretty similar to our debugAndroid task, except that we’re also passing a few more parameters to the build command. We pass details of our signing key to the build task in the form of a file called android-release-signing.properties. This file, sits at the root of our project and looks like this:

key.store=NAMEOFFILE.keystore
key.alias=NAMEOFALIAS

key.store is the name of the keystore file you’ll be using, which should sit at the same level as the properties file. key.alias is the name of the alias used when the key was created. Note that your password is not included in this file and you’ll be prompted to enter it when you do the build. This file is safe to commit into your git repo, but it is not advised that you commit your keystore file.

There is much more info on creating a signing key available but to get up and running quickly you can use:

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000