Optimizing Gradle for Multi-Flavor Projects: Custom Task Implementation and Handling Multiple GoogleService.json

Manish Singh
5 min readJul 12, 2024

--

1. Introduction

In Android app development, Optimizing Gradle build scripts is crucial for enhancing build performance and managing project complexities efficiently. This document outlines strategies for optimizing Gradle scripts, utilizing multi-flavor configurations dynamically, exploring Gradle tasks, and dynamically accessing variables for the AndroidManifest.xml file to run automatically for different environments like Production and Lower environments and create dynamic google json file for multi-flavor environment .

1.1 Objective

The objective of Optimizing Gradle in Android is to:

  • Utilizing multi-flavor configurations .
  • dynamically accessing variables for the AndroidManifest.xml to automate build process
  • Automate build process when we are using CICD to generate build
  • create a dynamic google json file for a multi-flavor environment.

1.2 Benefits

  • Gradle Optimization: Gradle optimization involves improving the build performance of your Gradle-based projects. This can include tasks such as configuring parallel execution, optimizing dependency resolution, and minimizing unnecessary tasks.
  • Multi-Flavors: In Gradle, flavors allow you to build multiple versions of your Android application from a single codebase. You can dynamically use these flavors by defining flavor-specific configurations and resources in your Gradle build scripts.
  • Gradle Task: Gradle tasks are the basic units of work in Gradle builds. Task exploration involves understanding the available tasks in your Gradle project and how they relate to each other. You can explore tasks using the gradle tasks command or by inspecting the project’s Gradle build scripts.
  • Dynamic Use of Variables for Manifest: Gradle allows you to access and manipulate variables defined in your AndroidManifest.xml file during the build process. You can achieve this using the manifestPlaceholders property in your build.gradle file to inject dynamic values into your manifest.
  • Handling Multiple GoogleService.json Files in Multi-flavor: When working with multiple flavors in Android projects, each flavor may require its own configuration files, such as GoogleService.json for Firebase integration. You can manage this by placing the respective configuration files in flavor-specific directories and configuring your Gradle build scripts to include them accordingly.

2. What is Task in Gradle

In Gradle, a task represents a single unit of work that needs to be performed, such as compiling source code, running tests, or generating documentation. Tasks can depend on other tasks, and they form the building blocks of the build process.

Here’s a simple code example demonstrating a Gradle task:

task greet {
doLast {
println 'Hello, Gradle!'
}
}
  • task greet: Declares a task named “greet”.
  • doLast { … }: Defines the action to be performed when the task is executed. In this case, it prints “Hello, Gradle!” to the console.
  • To execute this task, you can run the following command in the terminal:
/gradlew greet

Tasks can also have dependencies on other tasks. For example:

task prepare {
doLast {
println 'Preparing resources...'
}
}

task compile(dependsOn: 'prepare') {
doLast {
println 'Compiling source code...'
}
}

In this example:

  • task compile(dependsOn: ‘prepare’): Declares a task named “compile” that depends on the “prepare” task.
  • When you run ./gradlew compile, Gradle will first execute the “prepare” task, followed by the “compile” task.

2.1. Handling Multi-Flavouring in Gradle:

Handling multi-flavoring in Gradle allows you to create different versions of your app with variations in features, resources, or configurations.

Below is an example of how you can set up multi-flavoring in Gradle for an Android project:

android {
flavorDimensions "version", "environment"
productFlavors {
free {
dimension "version"
applicationId "com.example.free"
versionName "1.0-free"
// Other configurations specific to the free version
}
paid {
dimension "version"
applicationId "com.example.paid"
versionName "1.0-paid"
// Other configurations specific to the paid version
}

// Define flavors for different environments
debug {
dimension "environment"
applicationIdSuffix ".debug"
versionNameSuffix "-DEBUG"
// Other configurations specific to the debug environment
}
release {
dimension "environment"
// Other configurations specific to the release environment
}
}
}

In above code example:

  • flavorDimensions: Defines dimensions for flavors. Each dimension represents a category of variations (e.g., version, environment).
  • productFlavors: Defines individual flavors within each dimension. Each flavor can have its own applicationId, versionName, and other configurations.
  • For version dimension: free and paid flavors are defined.
  • For environment dimension: debug and release flavors are defined.
  • applicationIdSuffix and versionNameSuffix: Allows appending suffixes to applicationId and versionName based on the flavor or build type.

You can then build specific variants using the combination of these flavors and dimensions. For example:

./gradlew assembleFreeDebug: Builds the debug version of the free flavor.

./gradlew assemblePaidRelease: Builds the release version of the paid flavor.

2.2 Handling Multiple GoogleService.josn

Handling multiple google-services.json files in a multi-flavor Android project typically involves configuring your project to use different JSON files for each flavor.
Here’s a step-by-step guide on how to achieve this:

Note : By following these steps, you can manage multiple google-services.json files in a multi-flavor

Android project efficiently. Each flavor of your app can now use its own Firebase configuration.

2.2.1 Organize Your google-services.json Files

Place each google-services.json file in the respective flavor directories inside the app module.

For example:

app/
├── src/
├── flavor1/
└── google-services.json
├── flavor2/
└── google-services.json
├── flavor3/
└── google-services.json
└── …

2.2.2 Update Your build.gradle File

In your app module’s build.gradle file, configure the google-services plugin to use the appropriate JSON file for each flavor.

Here’s an example:

android {
...
productFlavors {
flavor1 {
// Other flavor configurations
}
flavor2 {
// Other flavor configurations
}
flavor3 {
// Other flavor configurations
}
// Define more flavors if needed
}
}

2.2.3 Configure google-services.json File Location:

Inside the android block of your build.gradle file, you can specify the location of the google-services.json file for each flavor using the googleServices directive.

For example:

android {
...
flavorDimensions "version", "environment"
productFlavors {
flavor1 {
dimension "version"
// Other flavor configurations
}
flavor2 {
dimension "version"
// Other flavor configurations
}
// Define more flavors if needed
}

// Specify the location of google-services.json for each flavor
applicationVariants.all { variant ->
variant.outputs.all {
def flavor = variant.flavorName
if (flavor == null) return
def googleServicesJsonFilePath = "src/${flavor}/google-services.json"
googleServices { jsonFile = file(googleServicesJsonFilePath) }
}
}
}

This code dynamically sets the location of the google-services.json file based on the flavor for each variant.

2.2.4 Sync and Build:

After making these changes, sync your project with Gradle. Then, when you build your project for different flavors, Gradle will use the corresponding google-services.json file for each flavor.

2.4 Dynamic handling of Crashlytics and Dynamic Handling of manifest keys:

To dynamically handle Crashlytics for release and debug builds in an Android project, you can use conditional configuration in your Gradle build files.With this setup, Crashlytics will be automatically enabled for release builds, allowing you to collect crash reports and analytics data from production releases while excluding it from debug builds to prevent unnecessary data collection during development.

buildTypes {
release {

ext.enableCrashlytics = true //crashlitics true when it is release build

manifestPlaceholders = [CLEAR_TEXT: "false", crashlyticsCollectionEnabled: "true"] }
debug {
//crashlytics false when it is debug build
ext.enableCrashlytics = false
manifestPlaceholders = [CLEAR_TEXT: "true", crashlyticsCollectionEnabled: "true"]
}

}

Use manifestPlaceholders in manifest file dynamically disable Crashlytics no need to make it manual

<application
android:name="com.americana.me.App"
android:allowBackup="false"
android:extractNativeLibs="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="${CLEAR_TEXT}">
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${crashlyticsCollectionEnabled}" />//Use of manifest placeholder

3. Conclusion:

Optimizing Gradle build scripts, leveraging multi-flavor configurations dynamically, exploring Gradle tasks, and accessing variables from the AndroidManifest.xml file are essential aspects of Android app development. By following the techniques outlined in this document, developers can enhance build performance, streamline project management, and customize app behavior effectively across different build variants and environments.

--

--