Being able to deploy directly to an app store from a CI/CD pipeline is incredibly convenient, improves repeatability, and generally a good practice. But there are several pieces that have to be put in place to get it running. We’ve had to do it multiple times, so we ended up writing this up as a walkthrough for ourselves, starting almost from File > New Project. Hopefully it will be as helpful for the rest of the Xamarin/Maui community.
Most of this blog post was written using a Xamarin.Android application, but the same process works for Maui Android applications. This is focused on deployed AAB files, which is the preferred format (as of the time of writing, anyway).
This walkthrough assumes you have not published your app at all yet. If you are already building and publishing, and want to skip ahead to the part where you’re deploying directly from your DevOps Pipeline, start here.
Preparing your Android app for release
Before setting up your build pipeline, you first need to ensure that your Xamarin.Android application build properties are setup according to the necessary configurations, as well as to your application’s requirements.
First, create a custom configuration option for building your Android application to publish to the Google Play Store. Select ‘Configuration Manager’ under the build option dropdown as shown below:
Then on the popup dialog under ‘Active solution configuration’ select <New…>
Provide a name for your configuration. For this sample, we will name it ‘PlayStore’ Uncheck the option to ‘Create new project configurations’ and click OK.
Then edit the Configuration column for the Android project. Select <New…> under the Configuration column for the Android project and provide the same name ‘PlayStore.’ Uncheck the ‘Create new solution configurations’ option and click OK.
Now you’re all set to begin editing the Android options for publishing to the Google Play Store. Below are some of the properties that need to be altered:
- Disable debugging
In order to package and publish your Android application, theandroid:debuggable
property in the AndroidManifest.xml file needs to be set to false. A better way to do this is to use a conditional compile statement in your AssemblyInfo.cs as noted in the documentation here.
- Packaging your application
You can choose to package your application as an .apk or an .aab file. For this sample we will choose .aab which is the more recent packaging format. For more information on app bundles see.
Right click on your Android project and select Properties. Then choose Android Options as shown in the image below. Under Android Package Format select bundle.
- Disable ‘Use Fast Deployment’
This option should be disabled as it is only meant for debugging. To disable this option, navigate to Android Options and uncheck “Use Fast Deployment” as shown above. - Specify Compiler and Code Shrinker
Choosing the right compiler and code shrinker will speed up the startup time of your Android application. You can specify the compiler architecture and code shrinker on the Android Options dialog as well.
For this sample, we’ve chosen the d8 compiler that is paired with the r8 code shrinker. You can read more about the compiler and shrinker options here.
- Specify supported architectures
Specify which CPU your application supports by selecting the Advanced button at the bottom of the Android Options page.
Now that your application build properties are setup, you can now generate a keystore file for signing your application’s aab.
Generating a keystore file
To generate a keystore file right click on your Android project and select archive. This will bring up the Archive Manager and generate your application bundle.
Take note of the Bundle Format on the generated archive and ensure that it is set to aab. Also take note of the Version and Version Code properties. This will correspond to the android:versionCode
and android:versionName
properties in the AndroidManifest.xml file. These have to be updated for each app build published to Google Play. A convenient way to generate unique version numbers for CI/CD scenarios is the Mobile App Tasks for iOS and Android DevOps extension.
Now select the generated archive and click Distribute. The popup below should appear. Select Ad Hoc.
The ‘Signing Identity’ page will appear. Click on the + sign to create a new Keystore file. Enter the values for Alias and Password and make sure you store them in a save place as these values will also be used later when setting up the Azure Pipeline.
The keystore file generated will be saved in the folder C:\Users\<your-alias>\AppData\Local\Xamarin\Mono for Android\Keystore\Your_KeyStoreFileAlias. Now that you have generated the keystore file, select ‘Save As’ on the same popup and choose where you would like to save the signed aab file.
Now that you have your application bundle file, you can set up a new application in the Google Play Console to host your Android application.
It is important to note that you will have to add your generated bundle manually for the first time before setting up the Azure Pipeline to push newer versions to the console. This will be described below.
Setting up your application in the Google Play Console
After initial application setup, drop the first version of the application by selecting your application in the Dashboard and then selecting Testing > Internal testing. Select the option to “Create new release” and proceed to upload your aab.
Now that you’ve dropped your first version, you can add a list of testers on the ‘Testers’ tab. This step is also necessary to initialize the Google Play entry to allow subsequent deployments to succeed from Azure DevOps.
Connection between Azure DevOps and Google Play
The next step is to create a Google Cloud service account and install the Google Play DevOps extension to your Azure DevOps Organization. Note that this is installed at the Organization level, not the individual Project level.
The link above has instructions on how to go through the Google Play Developer Console to create a Google Play service account, and this page has a more in-depth walkthrough. That page is focused on publishing through App Center, so while the initial steps apply to both App Center and Azure DevOps, stop at the instructions specific to App Center.
Once the extension is installed in the Organization and the service account is created, follow the directions to create a Project-specific service connection. In the example below, we assume the service connection is called Google Play Publishing.
Completing the DevOps Pipeline configuration
For this section, we start with a standard Maui Azure Pipeline yaml created by DevOps (for instance, this from a Microsoft sample Maui app).
The two important changes are to sign the build and to publish the signed AAB.
First, upload your keystore as a secure file to your Pipeline Library. Define the keystore password, key alias and key password as secret pipeline variables, then update the build step to:
- task: DotNetCoreCLI@2
displayName: 'Build and sign Android target'
inputs:
command: 'publish'
projects: '**/*.csproj'
publishWebProjects: false
arguments: '-c $(buildConfiguration) -f net6.0-android -p:AndroidPackageFormat=aab -p:AndroidKeyStore=True -p:AndroidSigningKeyStore=$(keystore.secureFilePath) -p:AndroidSigningStorePass=$(<var name for keystore password>) -p:AndroidSigningKeyAlias=$(<var name for key alias>) -p:AndroidSigningKeyPass=$(<var name for key password>)'
If your build has other options (e.g. targeting net7.0), merge in the signing-related arguments.
Next, add the task to publish to Google Play:
- task: GooglePlayRelease@4
displayName: 'Publish to Google Play'
inputs:
applicationId: 'com.<your-org>.<your-app>'
serviceConnection: 'Google Play Publishing'
action: 'SingleBundle'
bundleFile: '$(outputDirectory)/*-Signed.aab'
track: 'internal'
userFraction: '1.0'
Depending on your build setup, you may need to tweak the bundleFile value. There are a bunch of other options for the GooglePlayRelease task, but this is enough for a basic scenario of publishing to the internal test track.
You will probably also want a branch filter on this task (if you don’t have one on the entire Pipeline) to avoid having dev branches accidentally overwrite more official builds that you want published to the specific track.
Run the Pipeline
Now run the Pipeline. It will likely be blocked until you click through to give it permission to resources. This has to be done once, then subsequent runs will go through without manual intervention.