Automatic versionCode generation in Android Gradle

In this post I’ll show you how to setup Gradle for an Android project to automatically generate a usable versionCode from a standardized version number.

The problem

The Android Gradle build system is wonderful, with a good balance between sane defaults, readability and extensibility. However, handling of versionCode and versionName isn’t as automatic as one often would like. Thanks to the dynamic Groovy foundation of Gradle this issue is easy to remedy.

One problem is that you often want to implement a sane pattern of generating the versionCode value. This isn’t as easy as it might appear as there are a couple of things to take into consideration, many of which ties into how Google Play handles the versionCode.

  • The version code needs to increase for each release.
  • You can only upload one APK per version code, so it needs to be unique for each release.
  • Sometimes you want to use Release Candidates for alpha and beta testing, so these needs to have unique version codes.
  • The version codes of your Release Candidates must however be lesser than the version code of the final release.
  • You also want to be able to release point/patch releases of older apps, with lesser version codes than the latest version.

The last point is especially interesting due to the nature of the minSdkVersion value. To summarize this problem: you might have older version of your app active on Google Play, since these run on older devices than your latest version. If a bug appears, you want to be able to push out a fix even for these older versions, with a version code lower than the latest version.

The solution

The solution to all of this is to adapt a standardized versioning schema. There’s no need to use a too complex schema; we just want something that covers our use case. The chosen schema is therefore a simplified version of the Maven versioning schema. This schema uses Major, Minor and Patch versions, together with -SNAPSHOT and -RC suffixes. Versions will look like “1.0.1”, “1.2.0-SNAPSHOT”, “2.4.0-RC1” and so on.

  • The Major version denotes major changes to the app. If large parts are rewritten it warrants a Major version bump.
  • The Minor version denotes public releases. Each time a version has been released this number is bumped.
  • The Patch version denotes bug fixes and patches. It’s only ever used if an existing release needs to be amended.
  • “-SNAPSHOT” is used during development. These versions are never put on Google Play.
  • “-RC*” denotes Release Candidates. These are used during final testing, when the app is distributed externally, but not yet publicly. They lead up to the final release.
  • No suffix denotes a final release. The version code should always be higher than any Release Candidate.

The version name needs to be translated into a version code. We do this by allocating two digits to each version segment. That gives us 100 values for each one, with the Major version taking the highest numbers and the suffixes taking the lowest number.

So, version “1.2.3-SNAPSHOT” would then result in version code 1020300, whereas version “1.2.3-RC4” would result in version code 1020304. Final releases always gets assigned 99 as their last code, to make sure that they always have a higher version code than Release Candidates.

Example

Consider us working on our app, and that we’re currently working on version 1.2.0-SNAPSHOT. The version code is 1020000. As release nears we want to do a first test with a selection of users on Google Play, and we therefore change the version to 1.2.0-RC1, tag it, build it and put in on the alpha or beta channel on Google Play. Version code is now 1020001. Perhaps we need more fixes, so we do a 1.2.0-RC2 release. Version code is now 1020002.
Everything now looks good, so we change the version to 1.2.0, the final release, and pushes this to Google Play. Version code is now 1020099.

As soon as this is done we start on the next iteration, which is version 1.3.0-SNAPSHOT. Version code is 1030000. After some days a bug is found in the released version, and we need to push out a quick fix. We do this by creating a “1.2” branch from the latest release tag, fixing the bug, and set the version name to 1.2.1. Version code is 1020199.

Implementation

So, how to implement all of this in a smooth way? One approach is to do all of this manually. That’s of course doable, but we much rather make sure that as much as possible is automated. Gradle to the rescue then!

In our Android Gradle project we’ll start by seting the version in the gradle.properties file at the top level. Why? Well, because it’s a convention and there are existing Gradle tools which works with this approach. Thus, gradle.properties now contains

Gradle will automatically read from this file and make the version value available.

Now we have to add some code which can parse the version value and produce a versionCode value in accordance to the rules set up above. Create a file named versioning.gradle and put this code into it

This code isn’t activated by default, so we need to tell Gradle to do that. Alter the top level build.gradle file and add this line

Now for the final step; to configure the Android plugin. Edit the app/build.gradle file and change the android.defaultConfig section to read

And there you have it!
An example of this can be seen in this simple example project.

All code licensed under CC0.

This Post Has 16 Comments

  1. This is awesome, thanks! One thing to note: when copying directly from the site, versioning.gradle is full of non-breaking space characters (0xA0), which gradle doesn’t like :)

    1. I’m guessing that might be caused by the code highlighter we use.
      There’s a “Toggle plain code” button shown over the examples when you drag the mouse over them. That ought to remove the formatting; perhaps that also removes the non-breaking spaces?

  2. Thanks very much but I wonder how you deal with the problem of moving from production version code to dev (snapshot) version code?

    For example, suppose 1000099 is the latest version in Google Play. Now you want to start work on a patch release which would be 1000100. This is fine except that LICENSING and BILLING queries will fail because of the unknown version code. It seems that you have to stick with the previous versionCode or go through the cumbersome effort to upload a SNAPSHOT draft to Google Play.

    Having said that sticking with the previous versionCode has its own drawbacks. Using versionCode 1000099 which developing for 1.0.1 can result in accidental analytics being reported for a production version (you can code around this but need to be careful) and also you can’t do internal things like if (myVersionCode > 1000099) { do something cool }

    1. I’m not sure I follow what you mean by “LICENSING and BILLING queries”. I’m guessing you’re referring to a scheme where external servers provide content based on app data. Could you provide an example?

  3. Erik,
    This is really good article, I was stuck on this for long time and this saved me. excellent stuff.

    1. btw, one doubt, is it necessary that versioning.gradle needs to be in project? What if i keep it in some other location and call it with –init versioning.gradle file instead of adding it as plugin?

      1. That would also work. But be careful when doing this.
        Now you’ll have a project which will behave differently depending on where it’s built. Unless there are specific reasons for having system specific logic I would advise against spreading out the build logic outside of the project tree. You want to avoid any situations where you’ll have undocumented dependencies and hidden behaviour, and you want to strive for a as straight forward build process as possibly, where any new member only would need to check out the code and start the build.

  4. I find the overall approach of the problem very interesting; that is instead of controlling the version numbers (major, minor, patch, candidate) and creating the version name from these, you specify the version name instead and the version code is deduced.

    However, I find it difficult to imagine this approach in a setup with:
    – Git-flow as the workflow process
    – Play Store publishing for alpha, beta and release builds
    – CI with Jenkins for building and publishing all builds

    More specifically, the problem is with hotfix builds. Think of the following scenario:
    – You have a published version 1.2.0 (version code 1020099) on the production channel
    – You have a published version 1.3.0-RC5 (version code 1030005) on the beta channel
    – There’s a critical bug that requires that you release a hotfix version 1.2.1 (version code 1020199) on production

    The problem is that with the current Play Store publishing system the new hotfix version 1.2.1 has a version code smaller than 1.3.0-RC5, so it can’t be uploaded.

    I’d love to hear your thoughts on this.

    1. Yes, once you hit Google Play with any version you can’t go back to previous versions, even if it’s in a separate channel. It’s unfortunate that Google Play don’t allow apps in the beta and alpha channels to be separate from the ones in the release channel. I can’t really see any way around it in the current way Google Play is setup.

      It’s also worth noting that this scheme shouldn’t be used if want to supply different APKs depending on some device features (such as OpenGL support etc.). Google has some suggestion for another scheme: http://developer.android.com/google/play/publishing/multiple-apks.html#VersionCodes
      (though you can still script it, and use these scripts as a base, but you would need to alter the way the version code is calculated)

      But even with the scheme as suggested by Google for multi-apk you would still have the same hotfix issue.
      If you ever get into the situation you describe above it seems to me that the only recourse it to re-label 1.2.1 as 1.3.0 just to get it out, and then relabel what would have been 1.3.0 to something like 1.3.1 or 1.4.0. Kinda awkward…

  5. The way we decided to address the versioning problem is to have automatic version codes based on a timestamp. A suggestion would be the following:

    def buildTime() {
    return new Date().getTime()
    }

    def getY2KTime() {
    // 01/01/2000, 00:00
    return new SimpleDateFormat(“yyyyMMdd_HHmm”).parse(“20000101_0000”).getTime()
    }

    long currentBuildTime = buildTime()
    long y2kTime = getY2KTime()

    android {

    defaultConfig {
    versionCode((int) ((currentBuildTime – y2kTime) / 1000))
    versionName “${git-tag}”
    }

    }

    We have a jenkins job that continuously monitors for repo tag changes. Every time a new tag is pushed, jenkins builds that tag/commit. The version name may be the name of the tag itself, or deduced by it. We can have tags like “1.0.0” for release builds, “1.0.0-beta” for beta builds, etc, and jenkins will know on which Play Store channel to upload based on the tag.

    This gives us the flexibility we need to: 1) control the version name, 2) workaround the version code issues. However, we are not any more in control of the version codes.

  6. Hi Erik,
    We accidently deployed an app with a maximum version code (2147483647) into Google Play store, so far we couldn’t find anyway to remove or delete this app from store. Since we have new release is waiting for deployment. Do you have any suggestions or solutions for our current situation. We are so appreciate for your help!

    1. Sorry, that sounds like an issue only Google Play support could be able to handle.

  7. Stratos, interesting approach, but isn’t the point of tags to indicate successful builds?
    If you only build when you create a tag, then you are assuming the build will succeed.
    I could see how this approach might work on secondary/release builds if you have already run a primary/snapshot/test build.

    Also, are you kicking your build off using a “refs/tags/*” branch specifier?
    If not, then what is your best experience to do this?

    Thanks!

    Pv

  8. You are wasting one digit with this or alternatively you can look at it from the point of view that you have given major version three digits. We decided to give 3 digits for minor, ymmv but we have been building this app for like 1.5 years and are yet to hit that 1.0.0, so I’m estimating that at this phase it would take hundreds of years for us to get to version 100. Whereas new minor release should (at least in theory) happen every two weeks. Then if no major rewrites/releases would be done for about four years we might run out of minor versions. This might not be that realistic danger, but surely far more likely than to run out of majors, but to be able to run out of 3 digits minor version numbers would take decades which is again not going to happen.

    So, I changed this line:
    (major * 1000000) + (minor * 10000) + (patch * 100) + candidate;
    to:
    (major * 10000000) + (minor * 10000) + (patch * 100) + candidate;

    To be exact this means that the maximum version name/code is:
    v214.748.36-rc46
    That should last for several centuries if not millennia :)

Leave a Reply

Close Menu