This is a guide on how to build and deploy flutter hello world apps both iOS and Android from a private git repo. The code will be built in Azure devops pipeline and will be deployed to firebase for distribution on test devices.
flutter-helloworld-counterapp
Our initial goal is to setup a pipeline that will trigger when code is pushed to our git repo. For this we’ll start by adding a new Yaml file to our repo that will contain the code for the pipeline. This file will contain two jobs one for each platform ios and android. Since AzureDevops does not have native support for building Flutter apps, we’ll use a plugin from the marketplace.
Lets create our first job in the azureops.yaml file to build our iOS app without any code signing at this stage using the marketplace plugin. The azureops.yaml should like below
trigger:
- master
jobs:
- job: ios
pool:
vmImage: "macos-latest"
variables:
- name: configuration
value: "Release"
- name: sdk
value: "iphoneos"
- name: iosReleaseDir
value: $(Build.SourcesDirectory)/output/$(sdk)/$(configuration)
- name: ipaFile
value: $(iosReleaseDir)/hello_world_counter_app.ipa
steps:
- task: FlutterInstall@0
displayName: "Install Flutter"
inputs:
mode: "auto"
channel: "stable"
version: "custom"
customVersion: "2.2.3"
- task: FlutterBuild@0
displayName: "Flutter Build Apps - iOS"
inputs:
target: "ios"
buildName: "$(Build.BuildNumber)"
entryPoint: "lib/main.dart"
iosCodesign: false
Now to test this file. We’ll have to create a new pipeline in AzureDevops and point it to the azureops.yaml
inside this git repo. This can be done by clicking Create pipeline under pipelines and flow the sequence below: Connect > Azure Repos Git > Select > flutter-helloworld-counterapp > Configure > Existing Azure Pipelines YAML file > branch master > Path > /azureops.yaml > Run If the build is successful, next we’ll look at signing the app using a provisioning profile that has our test device.
To sign the built ipa, we need to login to Apple developer portal and create a provisiong profile and a p12 certificate. To start with, we need a certificate signing request
file (CSR). If you dont have one already, follow the instructions here to create one. Follow the steps below to create the provisioning profile:
com.example.helloWorldCounterApp
> Register. Note, this can also be found in the project.pbxproj under PRODUCT_BUNDLE_IDENTIFIER fieldPipelines > Library > Secure Files > + Secure File > Browse and upload both p12 and mobileprovision files. You can also create a variable group called secrets and store the p12 password as a secure variable if you chose a password whilst exporting the p12 from the keychain. Next add, the following yaml to azureops file to sign the build
Note: AzureDevops requires you to manually approve the use of the secure files first time they are referenced by the pipeline. This can be done via the GUI.
- task: InstallAppleCertificate@2
displayName: "Install Apple p12 cert"
inputs:
certSecureFile: "app.p12"
# certPwd: "$(certpassword)"
keychain: "temp"
- task: InstallAppleProvisioningProfile@1
displayName: "Install Apple Mobile Provisioning Profile"
inputs:
provisioningProfileLocation: "secureFiles"
provProfileSecureFile: "app.mobileprovision"
- task: Xcode@5
displayName: "Code Sign ipa for Distribution"
inputs:
actions: "build"
scheme: "Runner"
sdk: "$(sdk)"
configuration: "$(configuration)"
xcWorkspacePath: "ios/Runner.xcworkspace"
xcodeVersion: "default"
packageApp: true
signingOption: "manual"
signingIdentity: "$(APPLE_CERTIFICATE_SIGNING_IDENTITY)"
provisioningProfileUuid: "$(APPLE_PROV_PROFILE_UUID)"
To upload the signed ipa to firebase, we need to setup an ios project in firebase and then obtain an upload token that will use in our pipeline.
iOS Bundle ID | app nickname |
---|---|
com.example.helloWorldCounterApp | hellocounter ios app |
Download the GoogleService-Info.plist file as indicated on the console. Now open Xcode > Open a project or a file > Browse the Runner.xcworkspace file under the ios folder > Right click on the top Runner tree root item and select Add files to Runner.
Run the app locally once again to make sure nothing is broken by issuing flutter run
. The app should run as before. Next, lets obtain a firebase ci token by issuing the command firebase login:ci
. We will copy the generated token value and store that in azure devops as a secret variable under pipeline > library. Also lets create a group called secrets that will store all future secret variables. We will call this token firebasetoken
and save it under the secrets group. While we are at it, lets create a group called ‘general’ that will store all our non-secrets variables. Make sure to reference these variable groups in the pipeline yaml like below.
jobs:
- job: ios
pool:
vmImage: "macos-latest"
variables:
- group: secrets
- group: general
- name: configuration
value: "Release"
A few more things we need to setup before we can publish the app to firebase.
Set up a beta testers group with emails of testers
ios app distribution id - this can be seen under project settings of our helloworld-counterapps > under Your apps section > ios apps > App ID. Copy this ID and save that as a variable named iosFirebaseDistAppId
under the general group.
With all this setup, lets add the last task in our pipeline for distribution.
- task: Bash@3
displayName: "Upload to firebase app distribution"
inputs:
targetType: "inline"
script: |
npm i -g firebase-tools
ls -la $(iosReleaseDir)
firebase appdistribution:distribute "$(ipaFile)" \
--app "$(iosFirebaseDistAppId)" \
--release-notes "From Azure Devops" \
--token "$(firebasetoken)" \
--groups "beta-testers"
Push the code and if all the steps succeed, the testers in the beta testers group will be notified with the link to download the app, after they’ve accepted the invite. There might be additional steps to install the Firebase profile on the device, but the setup will guide you through the process. Below is the complete yaml file for reference.
trigger:
- master
jobs:
- job: ios
pool:
vmImage: "macos-latest"
variables:
- group: secrets
- group: general
- name: configuration
value: "Release"
- name: sdk
value: "iphoneos"
- name: iosReleaseDir
value: $(Build.SourcesDirectory)/output/$(sdk)/$(configuration)
- name: ipaFile
value: $(iosReleaseDir)/hello_world_counter_app.ipa
steps:
- task: FlutterInstall@0
displayName: "Install Flutter"
inputs:
mode: "auto"
channel: "stable"
version: "custom"
customVersion: "2.2.3"
- task: FlutterBuild@0
displayName: "Flutter Build Apps - iOS"
inputs:
target: "ios"
buildName: "$(Build.BuildNumber)"
entryPoint: "lib/main.dart"
iosCodesign: false
- task: InstallAppleCertificate@2
displayName: "Install Apple p12 cert"
inputs:
certSecureFile: "app.p12"
keychain: "temp"
- task: InstallAppleProvisioningProfile@1
displayName: "Install Apple Mobile Provisioning Profile"
inputs:
provisioningProfileLocation: "secureFiles"
provProfileSecureFile: "app.mobileprovision"
- task: Xcode@5
displayName: "Code Sign ipa for Distribution"
inputs:
actions: "build"
scheme: "Runner"
sdk: "$(sdk)"
configuration: "$(configuration)"
xcWorkspacePath: "ios/Runner.xcworkspace"
xcodeVersion: "default"
packageApp: true
signingOption: "manual"
signingIdentity: "$(APPLE_CERTIFICATE_SIGNING_IDENTITY)"
provisioningProfileUuid: "$(APPLE_PROV_PROFILE_UUID)"
- task: Bash@3
displayName: "Upload to firebase app distribution"
inputs:
targetType: "inline"
script: |
npm i -g firebase-tools
ls -la $(iosReleaseDir)
firebase appdistribution:distribute "$(ipaFile)" \
--app "$(iosFirebaseDistAppId)" \
--release-notes "From Azure Devops" \
--token "$(firebasetoken)" \
--groups "beta-testers"