When you are developing an app, you usually have a few different versions. The most common scenario is that you have a staging version that is used to manually test the app and assure its quality, and a production version. These versions usually have different settings. For example, the URL of the staging API can be different from the production API. In addition, you may have a free basic version of your app, and a paid version that has some extra features. In that case, you are already dealing with four different versions: staging free, staging paid, production free, and production paid. Having different configurations for every version can easily get very complicated.
Gradle has some convenient and extensible concepts to address this common issue. We already mentioned the debug
and release
build types that are created by Android Studio for every new project. There is another concept called product flavors, which adds even more possibilities for managing several versions of an app or library. Build types and product flavors are always combined, and make it easy to handle the scenario with free and paid versions of staging and production apps. The result of combining a build type and a product flavor is called a build variant.
We will start this chapter by looking at build types, what they can do to make a developer's life easier, and how to make the most of them. Then, we will discuss the difference between build types and product flavors and how both are used. We will also take a look at signing configurations, which is a necessity to publish apps, and how we can set a different signing configuration for every build variant.
In this chapter, we will cover the following topics:
In the Android plugin for Gradle, a build type is used to define how an app or library should be built. Every build type can specify whether the debug symbols should be included, what the application ID has to be, whether unused resources should be removed, and so on. You can define build types within a buildTypes
block. This is what a standard buildTypes
block looks like in a build file created by Android Studio:
android { buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
The default build.gradle
file for a new module configures a build type called release
. This build type does nothing more than disabling removal of unused resources (by setting minifyEnabled
to false
) and defining the location of the default ProGuard configuration file. This is to make it straightforward for developers to start using ProGuard for their production build, whenever they are ready for it.
The release
build type is not the only build type that is already created for your project, though. By default, every module has a debug
build type. It is set to sensible defaults, but you can change its configuration by including it in the buildTypes
block, and overriding the properties you want to change.
When the default settings are not enough, it is easy to create your own custom build types. All that is required for a new build type is a new object within the buildTypes
block. Here is an example of a custom build type called staging
:
android { buildTypes { staging { applicationIdSuffix ".staging" versionNameSuffix "-staging" buildConfigField "String", "API_URL", ""http://staging.example.com/api"" } } }
The staging
build type defines a new suffix for the application ID, making it different from the application ID of the debug and release versions. Assuming you have the default build configuration, plus the staging build type, the application IDs for the build types look like this:
com.package
com.package
com.package.staging
This means that you will be able to install both the staging version and the release version on the same device without causing any conflicts. The staging
build type also has a version name suffix, which is useful to differentiate several versions of the app on the same device. The buildConfigField
property defines a custom URL for the API, using a build configuration field, as we saw in Chapter 2, Basic Build Customization.
You do not always have to start from scratch when creating a new build type. It is possible to initialize a build type that copies the properties of another build type:
android { buildTypes { staging.initWith(buildTypes.debug) staging { applicationIdSuffix ".staging" versionNameSuffix "-staging" debuggable = false } } }
The initWith()
method creates a new build type and copies all properties from an existing build type to the newly created one. It is possible to override properties or define extra properties by simply defining them in the new build type object.
When you create a new build type, Gradle also creates a new source set. By default, the source set directory is assumed to have the same name as the build type. The directory is not automatically created when you define a new build type, though. You have to create the source set directory yourself before you can use custom source code and resources for a build type.
This is what the directory structure can look like with the standard debug
and release
build type, plus an extra staging build type:
app └── src ├── debug │ ├── java │ │ └── com.package │ │ └── Constants.java │ ├── res │ │ └── layout │ │ └── activity_main.xml │ └── AndroidManifest.xml ├── main │ ├── java │ │ └── com.package │ │ └── MainActivity.java │ ├── res │ │ ├── drawable │ │ └── layout │ │ └── activity_main.xml │ └── AndroidManifest.xml ├── staging │ ├── java │ │ └── com.package │ │ └── Constants.java │ ├── res │ │ └── layout │ │ └── activity_main.xml │ └── AndroidManifest.xml └── release ├── java │ └── com.package │ └── Constants.java └── AndroidManifest.xml
These source sets open up a world of possibilities. For example, you can override certain properties for specific build types, add custom code to certain build types, and add customized layouts or strings to different build types.
When adding Java classes to build types, it is important to keep in mind that this process is mutually exclusive. This means that if you add class CustomLogic.java
to the staging source set, you will be able to add the same class to the debug and release source sets, but not to the main source set. The class would then be defined twice, throwing an exception when you try to build.
Resources are handled in a special way when using different source sets. Drawables and layout files will completely override the resources with the same name in the main source set, but files in the values
directory (such as strings.xml
) will not. Gradle will instead merge the content of the build type resources with the main resources.
For example, if you have a strings.xml
file in the main source set that looks like this:
<resources> <string name="app_name">TypesAndFlavors</string> <string name="hello_world">Hello world!</string> </resources>
And if you have a strings.xml
file in the staging
build type source set like this:
<resources> <string name="app_name">TypesAndFlavors STAGING</string> </resources>
Then, the merged strings.xml
file will look like this:
<resources> <string name="app_name">TypesAndFlavors STAGING</string> <string name="hello_world">Hello world!</string> </resources>
When you build a build type that is not staging, the final strings.xml
file will just be the strings.xml
file from the main source set.
The same is true for manifest files. If you create a manifest file for a build type, you do not need to copy the entire manifest file from the main source set; you can just add the tags you need. The Android plugin will merge the manifests together.
We will talk about merging in more detail later in this chapter.
Every build type can have its own dependencies. Gradle automatically creates new dependency configurations for every build type. If you want to add a logging framework only for debug
builds, for example, you can do it like this:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.0' debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3' }
You can combine any build type with any dependency configuration in this manner. This gives you the possibility to get very specific with dependencies.