Appcelerator Blog

The Leading Resource for All Things Mobile

Versioning your apps in 4.1.0

9 Flares 9 Flares ×

Apps have two different types of version numbers plus there are differences between versioning for different platforms. Titanium 4.1.0 comes with better support for versioning iOS apps. Let’s explore how you would use this in an app for iOS and Android.

Release version number

First of all there’s the release version number. This is the version your users see. Each public release must have a unique version. Apple calls this the CFBundleShortVersionString and for Android this is android:versionName.

Both suggest (and to different lengths require) you to follow a subset of Semantic Versioning.

Semantic Versioning

In semantic versioning you use a string comprised of three period-separated integers. Normally the first integer would be incremented for breaking changes, but assuming your app won’t crash your users’ phones we typically use it to indicate new features or major changes. The second integer is for changes or less prominent features and the third one is incremented for fixes and other maintenance. For example:

  • 1.0.0: Initial release
  • 1.0.1: Fixed a few bugs
  • 1.1.0: Changed something
  • 2.0.0: Added a great new feature

Build version number

The other type of version number is the build version.

Before every public release, you probably go through several iterations of development and test builds that you distribute via alpha/beta testing on Google Play, TestFlight Beta Testing with Apple or another service like Installr.

This is where both Android and iOS (and others) do not follow semantic versioning anymore. If they did, the test builds for 1.1.0 would be versioned as 1.1.0-X where X can be many sorts of pre-release identifiers.

Instead, a separate build version is used. This version is normally not visible to the user and should be unique for each test build.

Format

Apple says you should follow the same simplified semantic versioning format for this CFBundleVersion as well, but the default for a new XCode project is 1. On Android android:versionCode must be an integer. This makes more sense, since if you would follow Apple’s guidelines then how would you version test builds for an upcoming 1.1.0 release? If you would use 1.1.1, 1.1.2 etc then what would the release after 1.1.0 be and the build versions for that? We’ll come back to that.

Versioning with tiapp.xml

The tiapp.xml has a tag that can be edited via Studio’s TiApp Editor.

Versioning via Studio

But there’s more.

iOS

Titanium will use version as-is for CFBundleVersion (build) and will try to format it as a three period-separated semantic version for CFBundleShortVersionString (release).

Until 4.1.0 you could not manually override this. The only way to set a different build version was to set version to something like 1.1.0.123 since Titanium would then normalize it to 1.1.0 for the release and leave it as-is for the build version number. This of course is not a proper semantic version, but Apple did accept it – allowing you to distribute multiple test versions via TestFlight leading up to a single release.

Now with 4.1.0 you can now manually override the values for both CFBundleVersion and CFBundleShortVersionString via the section in tiapp.xml:

< ?xml version="1.0" encoding="UTF-8"?>


  
  1.1.0.123

  
    
      
        CFBundleShortVersionString
        1.1
        CFBundleVersion
        123
      
    
  
Android

For Android, Titanium will use version as-is for android:versionName (release) but unlike iOS the build version (android:versionCode) is not based on version. You will have to set it manually via the android section in tiapp.xml or it will always default to 1, which is… well, not unique.

< ?xml version="1.0" encoding="UTF-8"?>


  
  1.1.0.123

  
    
    
  

Automating

All good developers are lazy, so lets automate this using a Grunt task. If you are not familiar with Grunt, read their Getting Started. We will be discussing Grunt and other task runners and how to use them for Appcelerator development in a later post.

The Grunt task here allows you to bump the major, minor, patch or just the build version – which will always be incremented. It assumes version to be major.minor.patch and to find only android:versionCode and CFBundleVersion in the android and ios sections so that Titanium will set release version numbers.

$ npm install grunt-cli
$ grunt version:minor
Running "version:minor" (version) task
Bumped version to: 1.2.0
Bumped android:versionCode to: 124
Bumped CFBundleVersion to: 130

Gruntfile.js

var fs = require('fs');

grunt.registerTask('version', function (what) {

    // map name to index and default to patch index
    var index = ['major', 'minor', 'patch'].indexOf(what);

    var tiapp = fs.readFileSync('tiapp.xml', {
        encoding: 'utf-8'
    });

    if (index !== -1) {

        tiapp = tiapp.replace(/()([^< ]+)(<\/version>)/, function (match, before, version, after) {
            version = version.split('.');

            // bump index and reset following
            for (var i = index; i < = 2; i++) {
                version[i] = (i === index) ? (parseInt(version[i], 10) + 1).toString() : '0';
            }

            version = version.join('.');

            grunt.log.writeln('Bumped version to: ' + version);

            return before + version + after;
        });

    }

    tiapp = tiapp.replace(/(android:versionCode=")([^"]+)(")/, function (match, before, versionCode, after) {
        versionCode = parseInt(versionCode, 10) + 1;

        grunt.log.writeln('Bumped android:versionCode to: ' + versionCode);

        return before + versionCode + after;
    });

    tiapp = tiapp.replace(/(CFBundleVersion< \/key>\s*)([^< ]+)(<\/string>)/mg, function (match, before, CFBundleVersion, after) {
        CFBundleVersion = parseInt(CFBundleVersion, 10) + 1;

        grunt.log.writeln('Bumped CFBundleVersion to: ' + CFBundleVersion);

        return before + CFBundleVersion + after;
    });

    fs.writeFileSync('tiapp.xml', tiapp);
});

Happy versioning!

9 Flares Twitter 0 Facebook 0 Google+ 3 LinkedIn 6 Email -- 9 Flares ×

9 Comments

  1. Italo Matos

    Hi, Crongrats!! Great article!
    I found just one fix on Gruntfile.js

    var fs = require(‘fs’);
    module.exports = function(grunt) {
    grunt.registerTask(‘version’, function (what) {

    // map name to index and default to patch index
    var index = [‘major’, ‘minor’, ‘patch’].indexOf(what);

    var tiapp = fs.readFileSync(‘tiapp.xml’, {
    encoding: ‘utf-8’
    });

    if (index !== -1) {

    tiapp = tiapp.replace(/()([^< ]+)()/, function (match, before, version, after) {
    version = version.split(‘.’);

    // bump index and reset following
    for (var i = index; i <= 2; i++) {
    version[i] = (i === index) ? (parseInt(version[i], 10) + 1).toString() : '0';
    }

    version = version.join('.');

    grunt.log.writeln('Bumped version to: ' + version);

    return before + version + after;
    });

    }

    tiapp = tiapp.replace(/(android:versionCode=")([^"]+)(")/, function (match, before, versionCode, after) {
    versionCode = parseInt(versionCode, 10) + 1;

    grunt.log.writeln('Bumped android:versionCode to: ' + versionCode);

    return before + versionCode + after;
    });

    tiapp = tiapp.replace(/(CFBundleVersion\s*)([^< ]+)()/mg, function (match, before, CFBundleVersion, after) {
    CFBundleVersion = parseInt(CFBundleVersion, 10) + 1;

    grunt.log.writeln(‘Bumped CFBundleVersion to: ‘ + CFBundleVersion);

    return before + CFBundleVersion + after;
    });

    fs.writeFileSync(‘tiapp.xml’, tiapp);
    });
    };

    • @Italo, what exactly is that fixing?

  2. Douglas Hennrich

    Gruntfile.js:18
    for (var i = index; i > SyntaxError: Unexpected token =
    Warning: Task “version:minor” not found. Use –force to continue.

    Aborted due to warnings.

    I’m getting this error when I type grunt version:minor

    • Douglas Hennrich

      fix it but now i’m getting this error:

      Loading “Gruntfile.js” tasks…ERROR
      >> ReferenceError: grunt is not defined
      Warning: Task “version:minor” not found. Use –force to continue.

      Aborted due to warnings.

      • @Douglas are you sure you have both global grunt-cli and local grunt installed?

  3. Here is an alloy.jmk that will bump your versions during compile:
    https://gist.github.com/mdpauley/4375a40a328a4f502ef9

    • @Michael did you test if that alloy.jmk results in the bumped version used in the Titanium compile following the Alloy compile? I’m asking because when Alloy runs as part of the Titanium compile the Titanium CLI will already have read the tiapp.xml file (with the old/existing version).

  4. قال:i have a question , i use Google Play on my gaxaly s ii and i open it via wi-fi , the internet connection is very speed , however the market loading very slowly , the market only but browsing is very good , anybody know why ?

    • I guess Google Play has some problems then?

Comments are closed.

Sign up for updates!

Become a mobile leader. Take the first step to scale mobile innovation throughout your enterprise.
Get in touch
computer and tablet showing Appcelerator software
Start free, grow from there.
9 Flares Twitter 0 Facebook 0 Google+ 3 LinkedIn 6 Email -- 9 Flares ×