By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.

Tuning Gradle to enable environment-dependent configuration

Andrés Schuster
May 14, 2024

Introduction

Gradle is a powerful build automation tool that allows to generate customizable builds for applications in general. It allows configuring different builds and flavors, and combining them to generate different bundle configurations. In this tech note, we will discuss different features it provides to configure a project to allow different build types for different environments.

Configuring the project

Gradle brings 2 different ways to make build variants, and each one of this options aims to specific goals:

  • buildTypes: every module has created by default both the `debug` and `release` buildTypes. These options allow configuring different types of build, divided for example by environment. Other environments - like `preproduction` or `staging` can be added as well.
  • productFlavors: This option is similar to the previous one, but aims to configure builds with different features, such as full and demos, or free and premium versions. Each productFlavor needs to have defined its flavorDimension, that specifies what configuration is aiming, for example, `paid` for free and premium, or `version` for demo and full. For example, you can define flags inside each flavor, to use them inside your code to enable or disable certain features. We can see this similar to compile-time feature flags. Modules have no productFlavors as default. 

Both of these can be setted inside the `build.gradle` file of the module we are configuring. If our project has N modules, we can have N build.gradle files in our projects, and we can define different configurations and properties in each build variant inside each file.

With these two options, Gradle allows you to combine buildTypes with productFlavors, naming the tasks with this nomenclature: <productFlavor><buildType>.

For example, if we have this 3 buildTypes, `debug`, `release`, `staging`, and this 2 productFlavors, `full`, `demo`, you will have 6 uniques configurations to build your application, with this names: fullDebug, fullRelease, fullStaging, demoDebug, demoRelease, demoStaging.

As an example, in one of our projects we have 3 different `buildType`s, and no `productFlavor` for the moment.
We use this buildTypes for our different environments (production, QA and development).
Here is a code example of how we define our build configuration:

We define a function accessible in every buildType, that receives the path for the properties file corresponding to the current environment, and sets all the properties defined inside of it - among other things, that will be described below. We define 3 buildTypes: debug, release and qaRelease. Inside qaRelease we declared a custom buildType, with the  matchingFallbacks property. That is used by Gradle to know which buildType should be used in other modules that don't have this buildType defined in its build.gradle file.

As it can be seen from the example, there might exist multiple .properties files that can be used in different configurations. Our criteria to create these properties files was the different environments that we use in our project, and that resulted in 4 properties files:

  • prod.properties: this file contains all the properties and configuration we need for our production environment (used by all the public that consumes our application).
  • qa.properties: this file contains all the properties and configuration we need for our QA environment. This is used by the developers and the QA team to test the application before pushing it into production.
  • dev-local.properties: this file contains all the properties we need for our local environment. This is used by our developers while testing or creating new features in their current workstation.
  • dev-local-ios.properties: we needed this file to build the application on macOS, mainly for paths and the way in which the app communicates with the backend works differently on this OS.

In each buildType, we also define some additional configuration:

  • signingConfig: used to configure the signing of the application.
  • minifyEnabled: used to turn on/off code shrinking.
  • proguardFile: loads the configuration to be used by ProGuard, a resource shrinker. Reduces the size of the application removing unnecessary classes, fields, methods and attributes, among other optimization steps.


Conclusion

In this article we explored several capabilities offered by Gradle to specify building and configuration details. Through the utilization of buildTypes and productFlavors, developers gain the capacity to create a spectrum of build variants, tailored to specific needs. This flexibility facilitates seamless management of builds across various scenarios, including production, QA, and development.

Interested in our services?
Please book a call now.