When developing Android applications, it’s common to build different versions of the same app to target various environments such as development, staging, and production. Android product flavors allow you to define separate configurations for each environment, making it easy to generate multiple APKs with different settings, such as base URLs, versioning, or feature flags.
In this article, we will show you how to integrate branch names dynamically into the version name of your APKs using product flavors. We’ll also walk through setting up a custom Gradle task to automate the build process for signed APKs and explain key parts of the code.
Setting Up Product Flavors
Aside from adding the branch name, we define the BASE_URL
and FRONT_URL
for each environment, ensuring that different URLs are used in production, development, and staging builds. Here’s how the defaultConfig
looks for this setup:
build.gradle (Module:app)
defaultConfig {
applicationId "com.example.app"
minSdkVersion 24
targetSdkVersion 34
versionCode 135
versionName "2.6.10"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk.abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
signingConfig signingConfigs.release
vectorDrawables {
useSupportLibrary true
}
// Define different base URLs and front URLs for various environments
buildConfigField "String", "PROD_BASE_URL", "\"https://api-prod.example.com\""
buildConfigField "String", "PROD_FRONT_URL", "\"https://www.example.com\""
buildConfigField "String", "DEV_BASE_URL", "\"https://api-dev.example.com\""
buildConfigField "String", "DEV_FRONT_URL", "\"https://dev.example.com\""
buildConfigField "String", "STAGING_BASE_URL", "\"https://api-staging.example.com\""
buildConfigField "String", "STAGING_FRONT_URL", "\"https://staging.example.com\""
}
The defaultConfig
section defines common settings such as applicationId
, SDK versions, and URLs for all environments. Different BASE_URL
and FRONT_URL
are defined for each flavor using buildConfigField
.
Branch Name Integration in Product Flavors
By incorporating the branch name in the APK version name, you can easily identify which branch the APK was built from. This can be particularly useful for tracking releases and managing versions across multiple environments. Below is the updated setup with branch name integration.
def branchName = getGitBranchName()
productFlavors {
production {
dimension "environment"
buildConfigField "String", "BASE_URL", "BuildConfig.PROD_BASE_URL"
buildConfigField "String", "FRONT_URL", "BuildConfig.PROD_FRONT_URL"
if (branchName) {
versionNameSuffix "-prod-${branchName}"
}
}
development {
dimension "environment"
buildConfigField "String", "BASE_URL", "BuildConfig.DEV_BASE_URL"
buildConfigField "String", "FRONT_URL", "BuildConfig.DEV_FRONT_URL"
if (branchName) {
versionNameSuffix "-dev-${branchName}"
} else {
versionNameSuffix "-dev"
}
}
staging {
dimension "environment"
buildConfigField "String", "BASE_URL", "BuildConfig.STAGING_BASE_URL"
buildConfigField "String", "FRONT_URL", "BuildConfig.STAGING_FRONT_URL"
if (branchName) {
versionNameSuffix "-staging-${branchName}"
} else {
versionNameSuffix "-staging"
}
}
}
In this setup, the getGitBranchName()
function dynamically retrieves the current Git branch name. If a branch name is available, it is appended to the versionNameSuffix
for each product flavor. This ensures that the APK version reflects the environment (production, development, or staging) and the branch it was built from.
This can be especially useful when debugging or deploying different versions of the app for testing purposes. You can quickly see which branch the APK came from, helping you better manage versioning across environments.
Task to Build Signed APKs with Detailed Explanations
Now that we have product flavors set up with branch name integration, we can create a custom Gradle task to build signed release APKs for all flavors. Below is the code for the task, along with explanations for each part.
task buildSignedReleaseApks {
group = "build"
description = "Builds signed release APKs for all flavors"
// Iterate over all application variants
android.applicationVariants.all { variant ->
// Check if the variant is a release build
if (variant.buildType.name == "release") {
def flavorName = variant.flavorName.capitalize()
// Find the build task for the current flavor
def buildTask = tasks.findByName("assemble${flavorName}Release")
// Add the build task as a dependency
if (buildTask) {
dependsOn buildTask
}
}
}
doLast {
// After the build, iterate over all application variants again
android.applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
def flavorName = variant.flavorName.capitalize()
// Define the path to the APK file that was just built
def apkFile = file("${buildDir}/outputs/apk/${variant.dirName}/${variant.name}.apk")
// Define the new path for the APK file, renaming it with the version name
def newApkFile = file("${buildDir}/outputs/apk/release/${flavorName}-${variant.versionName}.apk")
// Check if the APK file exists
if (apkFile.exists()) {
// Copy the APK file to the new location and rename it
copy {
from apkFile
into newApkFile.parent
rename { newApkFile.name }
}
}
}
}
}
}
Explanation of Key Parts:
- android.applicationVariants.all: This method iterates over all the build variants (i.e., combinations of build types and product flavors) defined in the project.
- variant.buildType.name == “release”: This checks if the current variant is a release build.
- def flavorName = variant.flavorName.capitalize(): The
flavorName
variable is set to the capitalized version of the flavor’s name (e.g., “Production”, “Development”). - tasks.findByName: Finds the Gradle task responsible for assembling the APK for the current flavor. The task name is dynamically generated by combining “assemble” with the flavor name and “Release”.
- dependsOn buildTask: Adds the
buildTask
as a dependency to ensure that the APK is built before this task proceeds. - apkFile: Specifies the path to the APK file that was just generated.
- newApkFile: Specifies the new location and name for the APK file, appending the flavor and version name to the filename.
- copy: Copies the APK file to the new location and renames it according to the defined rules.
Advantages of Using Product Flavors with Branch Name Integration
- Environment-specific Configurations: You can easily manage different configurations (e.g., URLs, API keys) for each environment.
- Versioning with Branch Names: By integrating the branch name into the version name, you can clearly identify the source branch for each APK.
- Automated Builds: The custom task simplifies the process of building signed APKs for all product flavors and renaming them based on the flavor and version.
Product flavors and branch name integration provide a robust solution for managing multiple environments and streamlining the release process in Android projects. By using the custom task to build signed APKs, developers can automate their workflow, improve version tracking, and reduce manual effort.
For more insights into Android development best practices, check out Building an Android App with Gemini AI.
Did you like this article?
You can subscribe to my newsletter below and get updates about my new articles.