Azure Devops pipeline for multi product flavors Android App

Sumeet Kumar
4 min readApr 25, 2023

Azure Devops pipeline for android app using multi dimensions and multiple product flavors

Introducing Azure DevOps

The idea is to automate this process using a really good tool: Azure DevOps, this process is called continuous integration (continuous delivery / deployment).

This project is a simple example of how to implement parallel jobs in Azure Devops to support an arbitrary number of Gradle flavors and build variants of an Android app. It also shows how we would flavorize both sensitive and non-sensitive variant-specific properties.

In this example, I will use 2 flavor dimensions, product, environment.

Where product is for a product/company/brand/client name and environment is Dev/QA/UAT/PROD etc

flavorDimensions "product", "environment"

productFlavors {

// Environments
production {
dimension "environment"
buildConfigField "String", "API_URL", '"https://"'
buildConfigField "String", 'environment', '"production"'
}

preprod {
dimension "environment"
applicationIdSuffix ".uat"
versionNameSuffix " - UAT"
buildConfigField "String", "API_URL", '"https://"'
buildConfigField "String", 'environment', '"staging"'
}

quality {
dimension "environment"
applicationIdSuffix ".qa"
versionNameSuffix " - QA"
buildConfigField "String", "API_URL", '"https://"'
buildConfigField "String", 'environment', '"quality"'
}

dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix " - DEV"
buildConfigField "String", "API_URL", '"https://"'
buildConfigField "String", 'environment', '"dev"'
}

// PRODUCTS >>>>
productA {
dimension "product"
applicationId "com.test.producta"
versionCode 1
versionName "1.1"
}

productB {
dimension "product"
applicationId "com.test.productb"
versionCode 2
versionName "2.2"
}

productC {
dimension "product"
applicationId "com.test.productc"
versionCode 3
versionName "3.3"
}
}

By default gradle assemble command will create builds for all products with all environments and buildTypes.

To overcome we will specify to create build only for particular flavor/product likeassembleProductA this will create all builds for productA with all environments and buildTypes.

1. ProductADevDebug.apk

2. ProductADevRelease.apk

3. ProductAQualityDebug.apk

4. ProductAQualityRelease.apk

5. ProductAPreprodDebug.apk

6. ProductAPreprodRelease.apk

7. ProductAProductionDebug.apk

8. ProductAProductionRelease.apk

Still we have debug apks that we don’t require in azure-pipeline, to overcome this, we will use build-variants variantFilter flag in build.gradle file to skip debug builds to optimize processing and reduce compilation time for azure pipeline.

Add below code in build.gradle file to skip debug builds from pipeline.

variantFilter { variant ->
if(variant.buildType.name == 'debug' || variant.buildType.name == 'Debug') {
setIgnore(true)
}
}

after running assembleProductAcommand you will see only following builds.

1. ProductADevRelease.apk

2. ProductAQualityRelease.apk

3. ProductAPreprodRelease.apk

4. ProductAProductionRelease.apk

Even you can change apk file name by adding below code in gradle file, this will append VersionName & VersionCode in apk file name.

android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = variant.name
outputFileName += "-v" + variant.versionName
outputFileName += "-" + variant.versionCode
outputFileName += ".apk"
}
}

Now Create a new Pipeline in Dev Ops to run the project

In Azure Devops, go to Pipelines -> New Pipeline -> Select your VCS -> select the repo -> Existing Azure Pipelines YAML file -> Branch = master or any other branch, Path = azure_devops/azure-pipelines.yml -> Continue -> Run.

Azure DevOps will automatically create a new file at the root of your project folder called azure-pipelines.yml, you will need to commit this file to your code repository. It contains the job definition for your project defined using yaml, it will be interpreted by Azure DevOps to know how to build and sign your application.

What is interesting with this sort of job definition is :

  • You keep your build jobs with your source code using the azure-pipelines.yml
  • yaml is easy to read and maintain
  • If you migrate to another Azure account you keep your jobs configurations

Default configuration for gradlew is to assemble, it will assemble all builds for all flavors, we will modify as per requirement and add assembleProductA in Tasks in gradlew configuration as shown below

Azure DevOps Pipeline for multi dimension android project

And gradlew YAML will look like

steps:
- task: Gradle@3
displayName: 'gradlew assembleProductA'
inputs:
gradleWrapperFile: '$(Parameters.wrapperScript)'
workingDirectory: '$(System.DefaultWorkingDirectory)'
tasks: assembleProductA

You can also define jdk version and gradle option by adding jdkVersionOption: '1.8' or gradleOptions: '-Xmx3072m' in gradlew YAML file.

Add your signing configuration / keystore to sign release apk

- task: AndroidSigning@3
inputs:
apkFiles: '**/*.apk'
apksign: true
apksignerKeystoreFile: 'production.keystore'
apksignerKeystorePassword: 'keystorePassword'
apksignerKeystoreAlias: 'key123'
apksignerKeyPassword: 'aliasPassword'
apksignerArguments: --out $(Build.SourcesDirectory)/app/build/outputs/apk/vrelease/*.apk
zipalign: true

Generate the artifact then publish

Here I am using AppCenter to publish releases, you can use Google Play Store or any other platform to distribute app.

If everything is going well you will see something like this :

Ref Azure Gradle Publish Artifacts

note: this story was 2 years in draft as I am too lazy to publish

Happy coding!

--

--

Sumeet Kumar

Android Enthusiast | developing Android & iOS native apps from 10+ years. IoT | e-commerce | baking/fintech & retail domain expert. https://github.com/samigehi