Chapter 4. Creating Build Variants

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:

  • Build types
  • Product flavors
  • Build variants
  • Signing configurations

Build types

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.

Note

The debug build type has its own default settings to make it easy to debug. When you create your own build type, different defaults apply. For example, the debuggable property is set to true for the debug build type, but is set to false in any other build type you create.

Creating build types

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:

  • Debug: com.package
  • Release: com.package
  • Staging: 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.

Source sets

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.

Note

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.

Dependencies

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.255