Gradle build system
Last modified on Mon 13 Mar 2023

Gradle has been the build and dependency system for Android projects since Google introduced Android Studio.

The Gradle configuration file is named build.gradle. Each project contains a top-level configuration file which looks something like this:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

A top-level file contains a global configuration which is applicable to all modules inside your project. (If you don't know what a module is, check out the following article: Projects overview).

Your main module (usually named app) contains its own build.gradle. Its contents usually look like this:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        // default config
    }

    lintOptions {
       // lint options here
    }

    signingConfigs {
        release {
            // release config here
        }
    }

    buildTypes {
        // module build types
    }

    flavorDimensions 'api'

    productFlavors {
       // module flavors
    }
}

dependencies {
    // dependencies here
}

Let's break down the module's build.gradle into several parts:

1. Default config

Values defined in the defaultConfig block override those in AndroidManifest. defaultConfig elements are applied to all build variants, unless a build variant has its own defaultConfig specified. An example of a typical defaultConfig block can be seen below.

defaultConfig {
    applicationId "co.infinum.appname"
    minSdkVersion 14
    targetSdkVersion 23
    versionCode 3
    versionName "1.1.0"
}

2. Lint options

Lint is a static code analysis tool that checks your source files for potential bugs and optimization improvements. A lint check is mandatory on all projects.

The lintOptions block defines the configuration for lint. For more info, check out the lint support page.

The abortOnError flag must be set to true. You can disable some of the checks after your team leader's approval. All unapproved disabled checks and abortOnError false are subject to a yellow card.

lintOptions {
    disable 'InvalidPackage', 'MergeRootFrame', 'InconsistentLayout', 'ContentDescription'
}

3. Signing configs

signingConfigs can contain one or more configurations used to sign your APK. Each signing configuration should have the following properties:

signingConfigs {
    release {
        keyAlias '{aliasvalue}'
        keyPassword '{keyPasswordValue}'
        storeFile file('mykeystore.jks')
        storePassword '{storePasswordValue}'
    }
}

Based on the example above, you should reference the signing configuration block in the buildType block with signingConfig signingConfigs.release.

4. Build types

The buildTypes element controls how your app is built and packaged. By default, the build system defines two build types: debug and release. The debug build type includes debugging symbols and is signed with the debug key. The release build type is not signed by default.

If your app uses a feature whose key depends on signingKey, you should sign the debug build with your key instead of using the debug (default) key to make the development easier for other collaborators. This is done in the example below:

 buildTypes {
    debug {
        minifyEnabled false
        debuggable true
        applicationIdSuffix '.dev'
        signingConfig signingConfigs.release
    }

    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.release
    }
}

minifyEnabled defines if the build will be run with ProGuard. All release builds must be built with ProGuard. The proguardFiles property defines ProGuard configuration files.

5. Product flavors

Product flavors define a customized version of the app. A project can have multiple flavors (e.g., [paid | free] or the same app targeting different API endpoints, etc.).

The following example creates three flavors:

 flavorDimensions 'api'
 productFlavors {
    dev {
        flavorDimension 'api'
        applicationId 'co.infinum.appname.dev'
    }
    staging {
        flavorDimension 'api'
        applicationId 'co.infinum.appname.staging'
    }
    production {
        flavorDimension 'api'
    }
}

Product flavor objects are of the same type as defaultConfig and share the same attributes. This means that you can override any default value for a specific flavor.

6. Dependencies

Gradle projects can have dependencies on other components. These components can be external binary packages or other Gradle projects. To configure a dependency on an external library jar, you need to add the dependency on the compile configuration.

dependencies {
    compile files('libs/foo.jar')
    compile 'com.squareup.retrofit:retrofit:1.9.0'
}

7. Build variants

Each (build type, product flavor) combination is called a build variant. Projects with no flavors still have build variants, but the single default flavor is used, nameless, making the list of variants the same as the list of build types. To build the variant you want, you should select it from the AS menu on the left side.

Build variant menu
Build variant selection

8. Adding a build version to an APK file

Android Studio sets the name of the APK file based on the app name, build type, and flavor. For example, the default output looks like this: app-staging-debug.apk. If necessary, this can be changed by adding the following code snippet to build.gradle file:

android.applicationVariants.all { variant ->
    def appName

    if (project.hasProperty("applicationName")) {
        appName = applicationName
    } else {
        appName = parent.name
    }

    variant.outputs.each { output ->
        def newApkName

        if (output.zipAlign) {
            newApkName = "${appName}-${output.baseName}-${variant.versionName}.apk"
        } else {
            newApkName = "${appName}-${output.baseName}-${variant.versionName}-unaligned.apk"
        }
        output.outputFile = new File(output.outputFile.parent, newApkName)
    }
}

This code snippet iterates through all build variants and renames APK files to appName-buildType-versionName.apk. Modify this example depending on your needs.

9. Variant filtering

In case you don't want all variants to be available for building, you can filter them out like this:

variantFilter { variant ->
    def ignore = variant.buildType.name.equals('staging') && variant.getFlavors().get(1).name.equals('multiDex')
    variant.setIgnore(ignore);
}

The variantFilter block has to be declared inside the android block of your app's build.gradle file (not in the top-level one).