- and how to publish it to Test Flight and Google Play Console
You have created a .NET MAUI app but you have been building it on your laptop and now want to know how to send it to Testers or to the Store.
Environment
This blog post is covering building via Azure Devops but there are many other options, like Github.
In Azure Devops you can setup your own build agent by following this guide: https://learn.microsoft.com/sv-se/azure/devops/pipelines/agents/osx-agent?view=azure-devops
You can also use a hosted agent. Here are some different alternatives: https://learn.microsoft.com/sv-se/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software
For building a Maui app for iOS you need a Mac. In this build pipeline 'macos-14' is used.
pool:
vmImage: 'macos-14'
Global settings
To share settings between build pipelines or to just abstract away the settings you can use Library in Azure Devops.
In this example the variable group "Svetto" is used.
You define the variable group in your build pipeline like this:
variables:
- group: Svetto
This is all the setting which is used to build both for iOS and Android, the name of the files is also specified as variables.
For signing the Android app:
- AndroidKeyStoreAlias
- AndroidKeyStoreFile
- AndroidKeyStorePassword
For signing the iOS app:
- AppleCertificate
- AppleSigningIdentity
- iOSCertPassword
- ProvisioningProfile
The version (major.minor)
- ApplicationDisplayVersion
The files needed to sign the app are:
.p12 which is the Apple Certificate
.mobileprovision which is the Apple Provision Profile
.keystore which is used to sign the Android app
Version number
A version number contains of three digits {major.minor.build}.
The major and the minor are specified in the project file:
<ApplicationDisplayVersion>2.0</ApplicationDisplayVersion>
To make the build number unique build/runner in devops is often used but it's also possible to use the date like this:
<ApplicationVersion>$([System.DateTime]::Now.ToString('yyyyMMddHH'))</ApplicationVersion>
Preparing
Select Xcode version
If you want to use a specific version of Xcode you can use this inline script, here it's specified that Xcode 16 should be used.
- task: CmdLine@2
displayName: 'Selects a specific version of Xcode'
inputs:
script: 'sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer'
Install latest .net MAUI
- task: CmdLine@2
displayName: 'Install Latest .NET MAUI Workload '
inputs:
script: 'dotnet workload install maui'
You can adjust the --source to target specific versions of .NET MAUI workloads, and you can learn more about changing sources in the .NET MAUI Wiki (https://github.com/dotnet/maui/wiki/#install-net-6-with-net-maui).
Install apple certificate and provision profile
For iOS, you need a signing certificate and a provisioning profile. This guide walks you through how to obtain these files:(https://learn.microsoft.com/sv-se/azure/devops/pipelines/apps/mobile/app-signing?view=azure-devops&tabs=yaml#apple)
- task: InstallAppleCertificate@2
inputs:
certSecureFile: '$(AppleCertificate)'
certPwd: '$(iOSCertPassword)'
keychain: 'temp'
- task: InstallAppleProvisioningProfile@1
displayName: 'Install app store provisioning profile'
inputs:
provisioningProfileLocation: 'secureFiles'
provProfileSecureFile: '$(ProvisioningProfile)'
Add Android keystore key
For Android, you need keystore file and value of keystore password and keystore alias. This guide walks you through how to obtain these files: (https://learn.microsoft.com/sv-se/azure/devops/pipelines/apps/mobile/app-signing?view=azure-devops&tabs=yaml#sign-your-android-app)
- task: DownloadSecureFile@1
name: keystore
inputs:
secureFile: '$(AndroidKeyStoreFile)'
Building
Build iOS
In .NET 8, the dotnet publish command defaults to the Release configuration. Therefore, the build configuration can be omitted from the command line. In addition, the dotnet publish command also defaults to the ios-arm64 RuntimeIdentifier. The RuntimeIdentifier can also be omitted from the command line.
.NET MAUI apps produce executables for each Target Framework—these are native app packages with all dependencies/resources bundled in.
- task: DotNetCoreCLI@2
displayName: 'dotnet publish iOS'
inputs:
command: 'publish'
publishWebProjects: false
projects: '**/Svetto.sln'
arguments: '-f net8.0-ios -c Release -p:ApplicationDisplayVersion=$(ApplicationDisplayVersion) -p:ArchiveOnBuild=true -p:EnableAssemblyILStripping=false -p:RuntimeIdentifier=ios-arm64'
zipAfterPublish: false
Copy ipa to stage folder
- task: CopyFiles@2
displayName: 'Copy iOS artifact'
inputs:
Contents: '**/*.ipa'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
CleanTargetFolder: true
OverWrite: true
flattenFolders: true
Build Android
- task: DotNetCoreCLI@2
displayName: 'dotnet publish android'
inputs:
command: 'publish'
publishWebProjects: false
projects: '**/Svetto.sln'
arguments: '-f net8.0-android -c Release -p:ApplicationDisplayVersion=$(ApplicationDisplayVersion) -p:AndroidKeyStore=true -p:AndroidSigningKeyStore=$(keystore.secureFilePath) -p:AndroidSigningKeyPass=$(AndroidKeyStorePassword) -p:AndroidSigningStorePass=$(AndroidKeyStorePassword) -p:AndroidSigningKeyAlias=$(AndroidKeyStoreAlias)'
zipAfterPublish: false
Copy aab to stage folder
- task: CopyFiles@2
displayName: 'Copy android artifact'
inputs:
Contents: |
**/*Signed.aab
TargetFolder: '$(Build.ArtifactStagingDirectory)'
OverWrite: true
flattenFolders: true
Publishing
Publish staged files, connect them to the build task
If you want to attach the ipa/abb to the build task you can use this task to publish the artifact to the task, as a zipfile called 'drop'.
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
Publish to Apple TestFlight
Prerequisites
- In order to automate the release of app updates to the App Store, you need to have manually released at least one version of the app beforehand.
- The tasks install and use fastlane tools. fastlane requires Ruby 2.0.0 or above and recommends having the latest Xcode command line tools installed on the MacOS computer.
Read more about the extension here:
https://github.com/microsoft/app-store-vsts-extension/tree/master
- task: AppStoreRelease@1
displayName: 'Publish to Test Flight'
inputs:
serviceEndpoint: 'AppleTestFlight'
releaseTrack: 'TestFlight'
appIdentifier: 'xxxxxxxx'
appType: 'iOS'
appSpecificId: 'xxxxxxxx'
shouldSkipWaitingForProcessing: true
isTwoFactorAuth: true
Publish to Google Play Console
To publish a .NET MAUI Android app for Google Play distribution requires that your app package format is AAB, which is the default package format for release builds. To verify that your app's package format is set correctly:
In Visual Studio's Solution Explorer right-click on your .NET MAUI app project and select Properties. Then, navigate to the Android > Options tab and ensure that the value of the Release field is set to bundle:
The first time an AAB is submitted to Google Play, it must be manually uploaded through the Google Play Console. This enables Google Play to match the signature of the key on all future bundles to the original key used for the first version of the app. In order to upload the app through the Google Play Console, it must first be built and signed in Visual Studio.
- task: GooglePlayRelease@4
inputs:
serviceConnection: 'GooglePlayConsole'
applicationId: 'xxxxxxxx'
action: 'SingleBundle'
bundleFile: '$(build.artifactstagingdirectory)/*.aab'
track: 'internal'
isDraftRelease: true
Conclusion
Next year AppCenter will be deprecated so MAUI developers need to find other ways to share their app to testers and users. Publishing them to the test stores (TestFlight/Google Play Console) is the recommended way.
I hope this guide will help you. Even if it doesn't cover all details about certificates, configuration and setup in the stores.
Good luck!
Top comments (0)