in Programming, Tips

Gradle: Use variables for easily updating library dependencies

I avoid keeping string literal constants in my code, as much as I can. This applies to my build and configuration files as well.

When you start a new Android project, Android Studio creates a basic build.gradle file for your application module and a couple of necessary dependencies in the dependencies block. Apart from that, it does not give you much guidance as to how actually organize those dependencies. As a result, a project’s dependency list ends up being a huge list of strings, something like:

//...
compile "com.squareup.retrofit2:retrofit:2.0.0"
compile "com.squareup.retrofit2:adapter-rxjava:2.0.0"
compile "com.squareup.retrofit2:converter-goon:2.0.0"
// ... and lots more

With projects like Spring, you often have a whole bunch of dependencies, where the group and the version remain the same, and the only thing which gets changed is the artifactId. A group modifier change occurs relatively rarely (but may happen, which our com.squareup.retrofit2 is the perfect example of). More often, you need to update the version. For these things, I’d rather store the information that changes in a variable, and reuse the variable instead. Once you need to change the version, you only have to change the value of the variable, and it will be applied everywhere. Extracting version numbers to variables also allows you to makes it easy to create different dependency sets for different builds (e.g. a version of your app for older devices, might probably need older versions of its dependencies).

So how do we do it? Gradle’s ext block is a shorthand for project.ext and it allows you to add extra properties to a given project. It could be the perfect place to add our dependency metadata:

ext {
    libs = [
            retrofit: [
                    group: 'com.squareup.retrofit2',
                    version: '2.1.0'
            ]
    ]
}

The ext object can get as many maps as you want. Please, do notice the difference in use of curly ({) and square ([) brackets there. What we are supplying to the ext object is literally a function, which will get executed line by line. You can do just about anything within the curly brackets, and it will get executed. libs though is just a normal map/dictionary.

Eventually, this gets neatly plugged into the dependencies list, thanks to Groovy’s support for templates strings. All you need to do is replace the single quotation marks with double, and prefix the variables with a $ sign. The rest should work out of the box:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    /* Our Retrofit dependencies using the metadata defined above */
    compile "$libs.retrofit.group:retrofit:$libs.retrofit.version"
    compile "$libs.retrofit.group:adapter-rxjava:$libs.retrofit.version"
    compile "$libs.retrofit.group:converter-gson:$libs.retrofit.version"

    /* Other dependencies */ 
}

Now, updating a version number is just a matter of editing it once. You can even externalize this metadata, and make it available to all of your modules.