In my last article, I showed you how to create a Continuous Integration (CI) pipeline for Android using Azure. I encourage you to read it before continuing with this one, as there are some basic lessons that I shared in that post.
This is a continuation of it, where we are going to learn about how to create another pipeline but, in this case, we automate the process of uploading our Android app to Firebase App Distribution for our QA team or to the Play Store for our users.
First we are going to see how to implement this on Firebase, then with the Play Store, and stay till the end, as I want to show a bonus tip on how you can create your own Gradle task to use later in whatever CI/CD system you prefer or in your local machine.
Firebase App Distribution
For this pipeline, we are going to:
- Checkout the repo
- Set the username to whom the commit will belong
- Checkout branch and fetch any changes
- Set the Java JDK to use
- Write the
local.properties
file with pipeline variables - Assemble the app and run unit tests
- Update the version code with a Gradle Task
- Upload the app to Firebase
- Push the new version code to our repo with a Gradle Task
Overall, this pipeline is very similar to what we saw in the Continuous Integration (CI) post, with the difference that we are incrementing the version numbers of the app using Gradle tasks, and the step where we upload to Firebase the APK.
For this last step, we need to generate a Firebase token that we will store in the variables of our pipeline. Here you have a link to the documentation to learn how to get the token.
After that, we call the Gradle task generated by Firebase like we would do with any other in Azure, and our app is going to be available automatically for testers after any change to the develop
branch is applied.
Without further ado, here is the complete pipeline that follows the steps commented above.
# Android
# Build your Android project with Gradle.
# Add steps that test, sign, and distribute the APK, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/android
trigger:
- develop
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
persistCredentials: true
clean: true
- script: |
git config --global user.name "MyService"
git config --global user.email "myuser@email.com"
git config --global push.followTags true
- task: CmdLine@2
displayName: 'Checkout branch'
inputs:
script: |
export branchName=$(echo $(Build.SourceBranch) | cut -c12-)
git fetch
git checkout $branchName
git reset --hard origin/$branchName
- task: Bash@3
displayName: 'Use JDK11 by default'
inputs:
targetType: 'inline'
script: |
echo "##vso[task.setvariable variable=JAVA_HOME]$JAVA_HOME_11_X64"
- task: Bash@3
displayName: 'Config local.properties'
inputs:
targetType: 'inline'
script: |
echo KEY=$(KEY) > ./local.properties
- task: Gradle@2
displayName: 'Build app and unit test'
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'clean assemble testPreDebug testProRelease'
publishJUnitResults: true
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
- task: Gradle@2
displayName: 'Update version'
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'updateVersionCode'
publishJUnitResults: true
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
- task: Gradle@2
displayName: 'Uploading to Firebase'
env:
FIREBASE_TOKEN: $(FIREBASE_TOKEN)
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'app:appDistributionUploadPreDebug app:appDistributionUploadProRelease'
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
- task: Gradle@2
displayName: 'Push new version code to Git'
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'commitVersionProperties'
publishJUnitResults: true
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
YAMLRemember to change the permissions that we talked about in the previous post so that the user associated to the pipeline has also Contribute permissions to be able to push changes to the repo.
Play Store Distribution
This pipeline is like the last one, but now instead of uploading it to Firebase, we are taking our app to the Play Store.
The steps of this pipeline are:
- Checkout the repo
- Set the username to whom the commit will belong
- Checkout branch and fetch any changes
- Set the Java JDK to use
- Write the
local.properties
file with pipeline variables - Assemble the app bundle
- Update the version code with a Gradle Task
- Upload the app to the Play Store
- Push the change in the version code to our repo
# Android
# Build your Android project with Gradle.
# Add steps that test, sign, and distribute the APK, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/android
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
persistCredentials: true
clean: true
- script: |
git config --global user.name "MyService"
git config --global user.email "myuser@email.com"
git config --global push.followTags true
- task: CmdLine@2
displayName: 'Checkout branch'
inputs:
script: |
export branchName=$(echo $(Build.SourceBranch) | cut -c12-)
git fetch
git checkout $branchName
git reset --hard origin/$branchName
- task: Bash@3
displayName: 'Config local.properties'
inputs:
targetType: 'inline'
script: |
echo KEY=$(KEY) > ./local.properties
- task: Bash@3
displayName: 'Use JDK11 by default'
inputs:
targetType: 'inline'
script: |
echo "##vso[task.setvariable variable=JAVA_HOME]$JAVA_HOME_11_X64"
- task: Gradle@2
displayName: 'Update version'
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'incrementVersion'
publishJUnitResults: true
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
- task: Gradle@2
displayName: 'Assemble bundle'
inputs:
gradleWrapperFile: 'gradlew'
options: '--stacktrace'
tasks: 'clean bundleProRelease'
gradleOptions: '-Xmx3072m'
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
sqGradlePluginVersionChoice: 'build'
publishJUnitResults: false
- task: CopyFiles@2
displayName: 'Save generated bundle to Azure'
inputs:
contents: '**/app-pro-release.aab'
targetFolder: '$(build.artifactStagingDirectory)'
- task: PublishBuildArtifacts@1
displayName: 'Upload generated bundle to Azure'
- task: GooglePlayRelease@4
inputs:
serviceConnection: 'YourServiceConnection'
applicationId: 'com.test.app'
action: 'SingleBundle'
bundleFile: '**/app-pro-release.aab'
track: 'internal'
- task: Gradle@2
displayName: 'Push new version code to Git'
inputs:
gradleWrapperFile: 'gradlew'
tasks: 'commitVersionProperties'
publishJUnitResults: false
javaHomeOption: 'JDKVersion'
sonarQubeRunAnalysis: false
spotBugsAnalysis: false
YAMLThe difference with the previous pipeline is that here we are using the task GooglePlayRelease@4
which takes the applicationId
of our app, the release channel where we want to upload the app, in this case the internal track and the serviceConnection
.
This last attribute needs to be configured in Azure, following these instructions.
Bonus tip: Create your own Gradle Task
As you saw, for the process of updating the version code of the app, I’ve used a Gradle Task called incrementVersion
.
For example, in this Android project, I have a file that contains the version number in three variables: versionPatch
, versionMinor
, versionMajor
and versionCode
.
The next code snippet reads the file that contains these variables and increments them using Kotlin.
tasks.register("incrementVersion", Task::class) {
group = "versioning" // Use this to group similar tasks
doLast {
val versionProperties = java.util.Properties()
val versionFile = File("config/version/version.properties")
versionProperties.load(versionFile.reader())
val fos = java.io.FileOutputStream(File("config/version/version.properties"))
val versionCode = versionProperties.getProperty("versionCode").toInt()
versionProperties.setProperty("versionCode", "${versionCode.inc()}")
val versionPatch = versionProperties.getProperty("versionPatch").toInt()
versionProperties.setProperty("versionPatch", "${versionPatch.inc()}")
versionProperties.store(fos, null)
fos.close()
}
}
KotlinAnd that’s all! It’s nice to have these custom Gradle tasks because you can execute them easily in your local machine, but you can use bash scripts instead, and it’s going to work great too.
Featured image by AbsolutVision on Unsplash
If you want to read more content like this without ads and support me, don’t forget to check my profile, or give Medium a chance by becoming a member to access unlimited stories from me and other writers. It’s only $5 a month and if you use this link I get a small commission.