In this last installment we’re going to take a look at the Android specific pipeline getting your app all the way to the Google Play Store. In the first article of this series I set up a parent workflow that passes parameters and secrets down into the Android specific pipeline we’ll be creating here. Be sure to give that one a read before continuing to read this one!
The initial workflow initialization
Our first step is to take all the incoming variables and define them in our nested workflow. That way we can use them in the next steps. The same goes for the secrets. We define both of these at the start of our workflow in the workflow_call
node by defining them with both a type and name.
name: Android Publish
on:
workflow_call:
inputs:
dotnet-version:
required: true
type: string
dotnet-version-target:
required: true
type: string
xcode-version:
required: true
type: string
project-file:
required: true
type: string
project-folder:
required: true
type: string
build-config:
required: true
type: string
build-version:
required: true
type: string
secrets:
keystore-password:
required: true
keystore-alias:
required: true
keystore:
required: true
playstore-service-account:
required: true
Afterward, we specify to our pipeline that we intend to execute this publishing task on a Windows machine. We employ windows-latest in this scenario to ensure we have access to the latest tooling versions. Nevertheless, depending on the nature of your codebase, it might be advantageous to opt for an earlier version of the build agent in order to successfully set up your project build.
jobs:
publish-android:
runs-on: windows-latest
name: Android Publish
steps:
...
Setting up for a successful build
These steps collectively set up the development environment, install necessary tools, and prepare the project for subsequent actions like building, testing, or deploying. It sets the .NET version to use, checks out the code, installs the necessary .NET MAUI workloads and restores any additional external dependencies the project might have.
- name: Setup .NET ${{ inputs.dotnet-version }}
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ inputs.dotnet-version }}
- uses: actions/checkout@v3
name: Checkout the code
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Restore Dependencies
run: dotnet restore ${{ inputs.project-file }}
Setting up the Android-specifics
The following task involves configuring our environment for code signing using Google’s toolchain. This process commences with the decoding of the keystore file from our secrets and transferring it into the build environment. This keystore plays a crucial role in signing your Android app and is provided via the parent pipeline. To ensure proper functionality, the file must reside on the build agent’s filesystem rather than being stored within the pipeline’s secrets. Hence, the reason we are currently placing it in that location.
- name: Decode Keystore
id: decode_keystore
uses: timheuer/base64-to-file@v1
with:
fileDir: '${{ github.workspace }}\${{ inputs.project-folder }}'
fileName: 'ourkeystore.jks'
encodedString: ${{ secrets.keystore }}
Version the app
This stage facilitates the management of version information for a .NET MAUI application. The csproj
parameter should be directed to the specific location of the project file, while the version
parameter should be configured with an internal numerical value to guarantee the uniqueness of the application’s version. Each binary version uploaded to Google’s portal should have a sequentially higher version number. The displayVersion
parameter introduces supplementary information to the version, making it more user-friendly. The printFile
parameter is optional and can be employed to log the version that is ultimately utilized when set to true.
- name: Version the app
uses: managedcode/MAUIAppVersion@v1
with:
csproj: ${{ inputs.project-file }}
version: ${{ github.run_number }} # to keep value unique
displayVersion: ${{ inputs.build-version }}.${{ github.run_number }}
printFile: true # optional
Publishing the app
In the subsequent step, we primarily utilize the dotnet publish
command to both build and publish the Android app. This operation involves specifying the project file, the desired build configuration, and the target framework for Android. By instructing the publish
command to utilize a keystore and providing the path to the keystore previously downloaded to the build agent, we signal our intent to sign this binary. To accomplish this, we also provide the alias and password set during the keystore’s creation, which is not elaborated upon in this article.
- name: Publish MAUI Android AAB
run: dotnet publish ${{inputs.project-file}} -c ${{ inputs.build-config }} -f ${{ inputs.dotnet-version-target }}-android /p:AndroidPackageFormats=aab /p:AndroidKeyStore=true /p:AndroidSigningKeyStore=ourkeystore.jks /p:AndroidSigningKeyAlias=${{secrets.keystore-alias}} /p:AndroidSigningKeyPass="${{ secrets.keystore-password }}" /p:AndroidSigningStorePass="${{ secrets.keystore-password }}" --no-restore
Uploading to Google Play Console
The final, and certainly critical, step involves uploading the AAB file obtained in the previous step to Google’s Play Console platform. This is accomplished by setting up a service account within the Play Console, the information of which can then be exported as a JSON file. This JSON file serves as the secret passed into this nested workflow. Within this last step, we provide this JSON info, the path to where the AAB file was published by the dotnet publish command, along with any other relevant data to push our package into the Google Play Console.
- uses: r0adkll/upload-google-play@v1.0.17
name: Upload Android Artifact to Play Console
with:
serviceAccountJsonPlainText: ${{ secrets.playstore-service-account }}
packageName: ${{ inputs.package-name }}
releaseFiles: ${{ github.workspace }}\${{ inputs.project-folder }}\bin\${{ inputs.build-config }}\${{ inputs.dotnet-version-target }}-android\${{ inputs.package-name }}-Signed.aab
track: internal
And that concludes our look at the pipeline! In this article, I aimed to offer a comprehensive overview of the GitHub Actions workflow steps necessary for building a .NET MAUI application, with a particular focus on Android. The last step in the workflow ensures a smooth integration with Google’s Play Console platform for testing and distribution. If you have any questions, please feel free to contact me via social media.