Photo by Olia Nayda

How to build and sign your Flutter iOS application using GitHub Actions

Continuous integration with GitHub Actions

Posted by Damien Aicheh on April 22, 2021 · 10 mins

While developing your Flutter project hosted on GitHub you can easily set up a GitHub Actions to regularly build and distribute your application to your users.

Steps overview

To achieve your goal you have to follow these steps:

  • Install the Apple certificate and provisioning profile
  • Specify the correct version of Flutter
  • Restore the pub packages
  • Build the application
  • Sign it using the right Apple certificate
  • Generate a .xarchive
  • Generate an .ipa from the .xarchive
  • Share it to your users

To be able to complete this tutorial you need to:

  • have access to your Apple Certificate file (.p12) with the associated password
  • the provisioning profile

With that ready, let’s get started!

Introducing GitHub Actions

The idea is to automate this process using GitHub Actions, this process is called continuous integration.

The interest of using GitHub Actions is that:

  • It can be accessible from everywhere
  • You will not be in charge of server maintenance
  • You have all in one place: GitHub

We will create something called a GitHub Action that will allow you to generate your ipa with just single click.

Setup your own GitHub Actions

Let’s create our first workflow! Inside your project, you need to create a workflows folder inside the .github folder and then create a new file called: ios-release.yml for instance. This file will contain our first job called build_ios:

name: Flutter_iOS

on:
  push:
    branches: [main]

jobs:
 build_ios:
   runs-on: macos-latest

   steps:
   - name: Checkout the code
     uses: actions/checkout@v2

This job will be triggered when you push new changes on the main branch. The first step that we will do is to check out the code of our branch.

Install Apple Certificates

You need to use something called secrets. This allows you to store these files securely in order to access your .p12 certificate and your provisioning profile for your application. If you haven’t used secrets before, checkout this previous article.

Let’s define one for the .p12 certificate called P12_BASE64 and one for the associated password called P12_PASSWORD.

Next step is to install your Apple Certificate, to do so we will use an action from the community called apple-actions/import-codesign-certs@v1 and use our previously defined secrets:


- name: Install Apple Certificate
  uses: apple-actions/import-codesign-certs@v1
  with:
    p12-file-base64: ${{ secrets.P12_BASE64 }}
    p12-password: ${{ secrets.P12_PASSWORD }}

To sign the application we also need to have our provisioning profile installed:


  - name: Install the provisioning profile
    env:
      PROVISIONING_CERTIFICATE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
    run: |
      PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision

      echo -n "$PROVISIONING_CERTIFICATE_BASE64" | base64 --decode --output $PP_PATH

      mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
      cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles

As you can see above, this script has 3 operations:

  • Create the variable
  • Import provisioning profile from secrets
  • Apply the provisioning profile

Build the Flutter code

To be able to use Flutter in our workflow we need to install it. In order to reach this we will use another action from the community:

- name: Install and set Flutter version
  uses: subosito/flutter-action@v1.4.0
  with:
    flutter-version: '2.0.1'

We need to add this action and specify the version of Flutter we want to use. It’s recommanded to fix this version instead of using stable as value to avoid potential breaking changes when a new version is published.

Now we are able to restore the packages for our application:

- name: Restore packages
  run: flutter pub get

Once they have been retrieved, we can build the application in release mode without signing it:

- name: Build Flutter
  run: flutter build ios --release --no-codesign

In fact we will apply our certificates previously installed in the next step!

Generate a xArchive

First thing, open your iOS project using XCode and select the target and inside Signing & Capabilities make sure Automatically manage signing is unchecked so we can sign it using the certificates we want without editing the XCode project.

Next, when you sign an iOS application you don’t sign the pods associated to it so you need to specify it inside your Podfile like this:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
      config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
    end
  end
end

Before we can generate our xarchive, we need to resolve the Swift dependencies of our project. This is useful specially when you have some Flutter iOS plugins written in Swift.

- name: Build resolve Swift dependencies
  run: xcodebuild -resolvePackageDependencies -workspace ios/Runner.xcworkspace -scheme Runner -configuration Release

Now to create an xarchive you need to find some information that can be found in your Provisioning Profile or in your Apple Certificate:

  • The development team identifier
  • The UUID which is the identifier of your Provisioning Profile
  • The code sign identity

With all of that done, we can use it like this:

 - name: Build xArchive
   run: |
     xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner -configuration Release DEVELOPMENT_TEAM=YOUR_TEAM_ID -sdk 'iphoneos' -destination 'generic/platform=iOS' -archivePath build-output/app.xcarchive PROVISIONING_PROFILE=YOUR_UUID clean archive CODE_SIGN_IDENTITY="Apple Distribution: Damien Aicheh"

Generate an ipa

With the xarchive generated we are able to export it as an ipa. To achieve that we need to add a new file called ExportOptions.plist to our project to specify the export options.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>app-store</string> <!-- app-store, ad-hoc, enterprise, development -->
  <key>teamID</key>
  <string>YOUR_TEAM_ID</string>
	<key>signingStyle</key>
	<string>manual</string>
  <key>provisioningProfiles</key>
	<dict>
		<key>YOUR_BUNDLE_ID</key>
		<string>YOUR_UUID</string>
	</dict>
</dict>
</plist>

Depending on your project configurations, you may have to add some more options to this file. You can have one ExportOptions.plist file for each environment of your project if you need that.

Then just run this command line and your ipa will be generated:

- name: Export ipa
  run: xcodebuild -exportArchive -archivePath build-output/app.xcarchive -exportPath build-output/ios -exportOptionsPlist ios/ExportOptions.plist

Publish the artefacts

To get access to the ipa generated previously from the GitHub interface let’s add this final action:

- name: Publish iOS Artefacts
  uses: actions/upload-artifact@v1
  with:
    name: release-ios
    path: build-output/ios

This will publish the ios folder that contain our package. Then you can install it on your device.

Publish Artefacts

Final touch

Now you are ready to share your application with your users depending on the context of your project! You will find an example code in this Github repository.

Do not hesitate to follow me on to not miss my next tutorial!