So you just started using Flutter and already love its wonderful and numerous widgets, but as your project is being serious you're wondering how to sign your Android & iOS apps and how to deploy them to both stores ? Oh, and as a lazy good developer, you're obvisouly wondering how to automate your builds and deployments to reduce manual actions (and errors) and free up some of your precious time to code your beautiful app ?
Well, me too. At first I thought it would be very simple and I'd just have to configure the awesome Codemagic, use its 500 free minutes per month... and relax š
After all, it's supposed to be magic š§ !
In this article I will explain step-by-step how to easily automate app building and beta deployment to Android & iOS stores.
āļøš
- I am not claiming to have the best approach of all, but I needed a reminder of all the steps needed and I thought it could benefit others... tell me if you have better ways to do it or if I can improve my solution !
- In this article we will only explore how to deploy to testing tracks (beta track for iOS and internal tests for Android), but the process should be pretty much the same for other tracks.
The various chapters we will detail are the following:
- š„ Prerequisites: all the accounts and things you need before setting up the CI
- š§āāļø Codemagic setup: setup of the CI workflow(s)
- š¤ Android setup
- šš± iOS setup
š You can find my demo repository on Github.
Prerequisites
Hopefully you are reading this article before you actually need the CI, because asking all people in the company to create the various accounts may take some time.
š„ Accounts
In order to connect to different platforms through Codemagic you will need a few accounts and keys to be setup before you start implementing actual CI pipeline.
In particular you need the following accounts and keys:
-
Apple enrolled account. You basically need a paid Apple Developer account, either enrolling as an individual or as an organization.
- Example: support@your-company.com
-
Apple Developer account: Used to handle iOS profiles & certificates and to deploy iOS builds to TestFlight.
- This account must have App Manager role (not juste Developer role).
- This account will be the same as the enrolled account if you're an individual, otherwise you should probably keep things separate and create this account as a "technical account".
- Example: apple-technical@your-company.com
-
Google Play Developer enrolled account. Again, you need a paid Google Play Developer account if you want to publish your apps.
- Example: support@your-company.com
-
Google Play Console service account key. A JSON key used to deploy Android builds to Google Play beta store.
- This account must have Release Manager role.
- This key will later be used in Google Play service account key chapter.
-
Google Play Console account: your personal account
- This account must have at least Release Manager role.
- Strictly speaking this account won't be used by Codemagic (it will only use service account JSON key mentionned above) but you will need it to manually upload your first build or to to fill app information in Google Play Console, promote the build to production and so on.
- Example: Google developer account like your.name@your-company.com
-
Gitlab/Github/Bitbucket account. Used by Codemagic to checkout Flutter app's repository.
- Make sure this account has access to your repository by checking repository Members (e.g. in Gitlab members)
- You need to generate SSH key pair and upload the public key to your profile (e.g. Gitlab SSH keys). You can name the public key anything, I like to put something like
Codemagic app XXX
- Keep the private key, you will need to encrypt it and pass it to Codemagic later on !
- Example: codemagic@your-company.com
-
Codemagic account. Used to configure Codemagic pipeline itself, Codemagic account is required to run any build as each account has a limited number of build minutes available (500 by default).
- For simplicity reasons I use the same Gitlab account mentioned above to connect to Codemagic (e.g. codemagic@your-company.com Gitlab account).
Configuration
Once you have your personal accounts for both Apple Connect and Google Play Console, you should take some time to create the application page for both stores as you won't be able to deploy Android or iOS test apps before you filled a minimum information:
-
Google Play Console : create a new application in the console.
- Make sure you enter a correct package name, it's definitive !
-
Google Play Console : fill-in basic information. Start at app's dashboard and follow the guide to fill all required steps (there are a lot of steps but it's pretty simple)
- You will need to upload at least an app icon (make sure you respect appropriate format), a Play Store commercial banner and 2 screenshots of the app before you can upload your first APK (yes, even for internal tests).
-
Apple Developer identifiers : create a new identifier of type
App IDs
and follow the steps.- Note generated
bundleId
andApp team id
, you will need them later. - Make sure you enter a correct bundle id, it's definitive !
- Note generated
-
Appstore Connect : create a new application in the console. You will need to link your new app to a
bundleId
... the one you created in previous step š¤ - Appstore Connect : fill-in basic information.
š§āāļø Codemagic setup
Codemagic use multiple files to do its "magic":
- codemagic.yaml main file: defines various worflow for iOS, Android and both apps deployments.
- Codemagic post-clone setup file: various installation steps for both Android and iOS apps
-
Gemfile for Android and for iOS. Used to automate gem installation on Codemagic using
bundle install
command. - Fastlane for Android and Fastlane for iOS: we use Fastlane for all build and deploy steps to help us handle certificate management, app signing and similar boring stuff.
Let's start right away with Codemagic main file, codemagic.yaml:
workflows:
internal-deployment:
name: Internal deployment
max_build_duration: 90
environment:
vars:
BUILD_NAME: 1.2.3
GOOGLE_PLAY_STORE_JSON_BASE64: Encrypted(var)
ANDROID_KEYSTORE: Encrypted(var)
ANDROID_KEYSTORE_PASSWORD: Encrypted(var)
ANDROID_KEY_ALIAS: Encrypted(var)
ANDROID_KEY_PASSWORD: Encrypted(var)
IOS_APP_ID: com.company.somename
APPLE_DEVELOPER_TEAM_ID: 123ABCD456
FASTLANE_EMAIL: apple-technical@your-company.com
FASTLANE_PASSWORD: Encrypted(var)
MATCH_PASSWORD: Encrypted(var)
SSH_KEY_FOR_FASTLANE_MATCH_BASE64: Encrypted(var)
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: Encrypted(var)
flutter: stable
xcode: latest
cocoapods: default
cache:
cache_paths:
- $HOME/Library/Caches/CocoaPods
- $HOME/.gradle/caches
- $FLUTTER_ROOT/.pub-cache
triggering:
events:
- tag
scripts:
- name: Post clone setup for Android & iOS
script: |
#!/usr/bin/env sh
/bin/sh $FCI_BUILD_DIR/codemagic/post-clone.sh all
- name: Build & Deploy Android app
script: |
cd $FCI_BUILD_DIR/android
bundle exec fastlane supply init --track internal --package_name com.company.somename
- name: Build & Deploy iOS app
script: |
cd $FCI_BUILD_DIR/ios
bundle exec fastlane ios beta
artifacts:
- build/**/outputs/**/*.apk
- build/**/outputs/**/*.aab
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- flutter_drive.log
publishing:
slack:
channel: '#flutter-codemagic-demo'
notify_on_build_start: false
If you're familiar with Gitlab CI or YAML in general, you shouldn't be too shocked reading this file. Let's describe the behaviour of your one and only workflow, internal-deployment:
- š«
max_build_duration
: a timeout duration, because sometimes Codemagic/Android/iOS builds get stuck. - ā
environment
: all environment variables that will be used by our scripts. More on this in Android and iOS setup. Also defines the versions of Flutter, Xcode and Cocoapods that Codemagic should use. It is good practice to use latest stable versions. - š
cache
: files that should be cached and reused across multiple builds to save some precious build minutes. - š
triggering
: how the build should be triggered. I decided that mine should be triggered every time I release a new tag in my Gitlab project. Note that I had to add a Gitlab webhook in order to automatically trigger Codemagic on tag creation. - ā¶ļø
scripts
: the core of our workflow: what we should do when this Codemagic workflow is triggered. As you can see here we only delegate the work to Fastlane for both Android and iOS, as we will see in a minute. - š¦
artifacts
: the artifacts we want to publish on Codemagic. Not solely needed as artifacts will be published to iOS and Android consoles but I like to keep them here in case the build fails and I need to debug. - š©
publishing
: I like to be notified when my build fails or succeeds so I configured a Slack notification here.
The first script called by our worflow is a "post-clone" script, let's see this codemagic/post-clone.sh file:
#!/usr/bin/env sh
set -e # exit on first failed commandset
installGems() {
echo "Installing gems..."
bundle install
bundle update fastlane
bundle update signet
}
androidSteps() {
echo "========================================"
echo "| Android post-clone steps |"
echo "========================================"
# set up key.properties
echo $ANDROID_KEYSTORE | base64 --decode > /tmp/keystore.keystore
cat >> "$FCI_BUILD_DIR/android/key.properties" <<EOF
storePassword=$ANDROID_KEYSTORE_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
keyPassword=$ANDROID_KEY_PASSWORD
storeFile=/tmp/keystore.keystore
EOF
# set up local properties
echo "flutter.sdk=$HOME/programs/flutter" > "$FCI_BUILD_DIR/android/local.properties"
echo "--- Generate Google Service key for Android"
GOOGLE_PLAY_STORE_JSON_KEY_PATH="$FCI_BUILD_DIR/android/app/google-play-store.json"
echo $GOOGLE_PLAY_STORE_JSON_BASE64 | base64 --decode > $GOOGLE_PLAY_STORE_JSON_KEY_PATH
cd $FCI_BUILD_DIR/android
installGems
}
iosSteps() {
echo "========================================"
echo "| iOS post-clone steps |"
echo "========================================"
echo "--- Generate SSH key for Gitlab access from Fastlane Match"
echo $SSH_KEY_FOR_FASTLANE_MATCH_BASE64 | base64 --decode > /tmp/bkey
# adding custom ssh key to access private repository
chmod 600 /tmp/bkey
cp /tmp/bkey ~/.ssh/bkey
ssh-add ~/.ssh/bkey
cd $FCI_BUILD_DIR/ios
installGems
}
androidSteps
iosSteps
This script is pretty boring but what it does is install a bunch of gems and setup some files that iOS and Android will need for building, signing and publishing the apps.
As you can see there is a bundle install that will use Android Gemfile and iOS Gemfile to automatically install required gems:
Android Gemfile
source "https://rubygems.org"
gem "fastlane"
iOS Gemfile
source "https://rubygems.org"
gem "fastlane"
gem "cocoapods"
Let's now see Android build script in android/fastlane/Fastfile file:
default_platform(:android)
platform :android do
desc "Deploy a new internal build to Google Play"
lane :internal do
Dir.chdir "../.." do
sh("flutter", "packages", "get")
sh("flutter", "clean")
sh("flutter", "build", "appbundle", "--build-number=#{ENV["PROJECT_BUILD_NUMBER"]}", "--build-name=#{ENV["BUILD_NAME"]}")
end
upload_to_play_store(
track: 'internal',
aab: '../build/app/outputs/bundle/release/app-release.aab'
)
end
end
This script basically uses Fastlane to build and deploy the app to Google Play Console. First it defines a "lane", which is just Fastlane way of defining a "worflow". Then we use shell to initialize and build our flutter app, by passing it build name and number from environment variables.
Once the signed application is ready, we use Fastlane once again to publish this app to the internal Android track through upload_to_play_store task.
Pretty simple right ? š
Let's now see the equivalent for iOS:
default_platform(:ios)
platform :ios do
before_all do
# Create a local keychain that will later store iOS profiles and certificates
if is_ci?
puts "This is CI run. Setting custom keychain."
create_keychain(
name: 'Temp.codemagic_keychain',
password: 'Temp.codemagic_keychain_password',
default_keychain: true,
unlock: true,
timeout: 3600,
)
end
end
desc "Push a new beta build to TestFlight"
lane :beta do
# Synchronize profiles & certificates from Git repo using Match
match(
type: "appstore",
readonly: is_ci,
keychain_name: 'Temp.codemagic_keychain',
keychain_password: 'Temp.codemagic_keychain_password'
)
# Disable automatic code signing as we will use custom signing method later on
update_code_signing_settings(
use_automatic_signing: false
)
# Update Xcode provisioning profile with the one we got from Git repo using Match
update_project_provisioning(
# https://github.com/fastlane/fastlane/issues/15926
profile: ENV["sigh_#{ENV["IOS_APP_ID"]}_appstore_profile-path"],
build_configuration: "Release",
code_signing_identity: "iPhone Distribution",
xcodeproj: "Runner.xcodeproj",
)
# Replace version number with Codemagic build number
set_info_plist_value(
path: "Runner/Info.plist",
key: "CFBundleVersion",
value: ENV["PROJECT_BUILD_NUMBER"]
)
# Replace version name with our semver version
set_info_plist_value(
path: "Runner/Info.plist",
key: "CFBundleShortVersionString",
value: ENV["BUILD_NAME"]
)
# Run a first Flutter build with code signing disabled
Dir.chdir "../.." do
sh("flutter", "packages", "get")
sh("flutter", "clean")
sh("flutter", "build", "ios", "--release", "--no-codesign")
end
# Run a second Flutter build with custom code signing
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
configuration: "Release",
xcargs: "-allowProvisioningUpdates",
export_options: {
signingStyle: "manual",
method: "app-store",
provisioningProfiles: {
"#{ENV["IOS_APP_ID"]}": "match AppStore #{ENV["IOS_APP_ID"]}"
}
},
)
# Upload our build to TestFlight (Beta track)
upload_to_testflight(
skip_waiting_for_build_processing: true,
apple_id: "123456789"
)
end
end
Alright, let's decrypt this file step-by-step:
-
before_all
: Before all steps run we create a local keychain that will later store iOS profile and certificates for app signting. This keychain will only live the time of the Codemagic build so we don't really care what the password is. -
match
: We start the beta lane with profile and certificates synchronization. Codemagic configuration for iOS deployment can be rather complicated, in order to avoid this complexity by handling certificates and provisioning profiles ourselves, we use Fastlane and match for code signing. Here we basically tell match to retrieve all stored profiles and certificates from the repo we stored them previously, and store them into the keychain we created in the previous step. We tell match to get things forappstore
type, i.e. release profile and certificates. We will see later on in iOS setup how to initialize match to create and store profile and certificates. -
update_code_signing_settings
: We disable automatic code signing because we want to handle it manually using match-retrieved profile/certificates from previous step -
update_project_provisioning
: This step is essential as it tells Xcode to use our generated profile and certificates instead of its default (automatic) signing files. We tell it to use match retrieved provisioning profile instead. -
set_info_plist_value
: We replace Xcode version values with the correct version:-
PROJECT_BUILD_NUMBER
is the Codemagic global build number (global to your application, no matter which workflow you run). Useful to deploy the same version multiple times and from different workflows. -
BUILD_NAME
is the actual version of our project, in semver terminology. Note that it is up to you to handle the bump of this variable on each of your app's releases. In my case I use Gitlab pipeine with a custom script to automatically bumpBUILD_NAME
environment variable insidecodemagic.yaml
and commit this as a release commit.
-
-
flutter build ios --release --no-codesign
: Flutter setup with a full packages/clean and build process, but with codesign disabled. As stated in Flutter official CI/CD documentation, we need to initialize our project wiht a first Flutter build before we can actually run the signing build step with Fastlane (see next step) -
build_app
: We use Fastlane once again to build a signed version of our iOS application. Of course we tell it to use the provisioning profile we retrieved in previous step with match. -
upload_to_testflight
: We can finally use Fastlane upload_to_testflight task to automatically push our new.ipa
application to Apple TestFlight (for beta testing). The two parameters are set to avoid waiting for Apple build processing to end Codemagic build. It means that you are not 100% sure that your build has correctly been received (and is valid) by Apple but in the other hand it has 2 great advantages:- It consumes less Codemagic free minutes waiting for an external response that is not really part of the build itself
- It is the only way of only using an Application Specific Password to push your app. Otherwise you would need 2FA with a short-lived session token, and you don't want that on your CI. Really, believe me š
š¤ Android setup
Codemagic configuration for Android deployment is pretty straightforward and is rather well described in code signing documentations of both Flutter and Codemagic
You will need to create a few things:
- Create a service account key to interact with Google Play Console
- Create a keystore for Android app signing
- Configure Android project to use code signing for release mode, with appropriate keystore
- Locally generate a signed Android application and upload this first release to Google Play Console
1. Google Play service account key
Let's start by creating the service account key.
These steps are very well described in Codemagic documentation (in Google Play section).
This variable is going to be translated into Android google-play-store.json
by our codemagic/post-clone.sh
script:
GOOGLE_PLAY_STORE_JSON_KEY_PATH#"$FCI_BUILD_DIR/android/app/google-play-store.json"
echo $GOOGLE_PLAY_STORE_JSON_BASE64 | base64 --decode > $GOOGLE_PLAY_STORE_JSON_KEY_PATH
...which will used by Fastlane further down the road when deploying our application to Google Play Console.
ā ļø Only the app owner of Google Play Console project is able to create a service account key, make sure you ask this person to create (and send you) the JSON key !
We now need to create a file named android/fastlane/Appfile
to give Fastslane our Google Play service key:
json_key_file("app/google-play-store.json")
package_name("com.company.awesome_app")
Of course, you should put your Android package name here (lowercase letters and underscores only !)
ā¹ļø the actual JSON key will be populated later on by post-clone.sh
script
2. Keystore
Generate a keystore for current project:
keytool -genkey -v -keystore your_app.jks -alias your_app_key -keyalg RSA -keysize 2048 -validity 10000 -storetype JKS
You will prompted for a password.
ā ļø Don't forget to securely store the password and share it with your team if they need to sign the app using the keystore (e.g. for CI builds). š
ā¹ļø -storetype JKS
option is required for Java 9+ otherwise you will not be prompted for alias key/password and the keystore will not be compatible for code signing anyway...
Now that you have an Android keystore, you need to encode your keystore to base64.
base64 -i your_app.jks
3. Project configuration
Now that we have created util stuff needed to sign and publish our app to Google Play, we only need to tweak our configuration to reference info.
First open codemagic.yaml
and define the following variables:
-
GOOGLE_PLAY_STORE_JSON_BASE64
: [Codemagic-encrypted] - base64 encoded version of JSON key downloaded from Google Play Console in step 1 above. -
ANDROID_KEYSTORE
: [Codemagic-encrypted] - base64 encoded version of the keystore generated in step 2 above. -
ANDROID_KEYSTORE_PASSWORD
: [Codemagic-encrypted] - password of the generated store -
ANDROID_KEY_ALIAS
: [Codemagic-encrypted] - alias of the generated key -
ANDROID_KEY_PASSWORD
: [Codemagic-encrypted] - password of the generated key alias
ā ļø Variables that are marked as [Codemagic-encrypted] must be encrypted using Codemagic user interface.
Go to any project
> configuration wheel
> Encrypt environment variable
and paste the variable you want to encrypt, and copy it back to codemagic.yaml
.
Keep the Encrypted(VAR)
around your variable !
These variables are going to be translated into Android key.properties
by our codemagic/post-clone.sh
script:
echo $ANDROID_KEYSTORE | base64 --decode > /tmp/keystore.keystore
cat >> "$FCI_BUILD_DIR/android/key.properties" <<EOF
storePassword#$ANDROID_KEYSTORE_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
keyPassword=$ANDROID_KEY_PASSWORD
storeFile=/tmp/keystore.keystore
EOF
Finally, we can configure our android/app/build.gradle
to sign our app using provided keystore. First add these lines above android
tag:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
These lines tell Android to read keystore properties from our specific file. We can now add signing configs to the build (replace existing lines with these new ones):
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
You're ready to sign your Android app !
4. Signed release & first upload
In order to push automated releases of your Android app, you need to create a first release manually on the Google Play Console interface and upload a first signed bundle/apk.
You can follow these steps:
- Add a file named
/android/key.properties
with the following content:
storePassword=$ANDROID_KEYSTORE_PASSWORD
keyAlias=$ANDROID_KEY_ALIAS
keyPassword=$ANDROID_KEY_PASSWORD
storeFile=/tmp/keystore.keystore
Obvisouly you need to replace the values of each property with the actual value you generated (same values as you set for corresponding environment variable in Project Configuration section).
- Make sure
/android/key.properties
is excluded in your/android/.gitignore
. Flutter does this by default when creating the app, you should see the following line:
key.properties
If you don't have this line, obviously add it, it is crucial that you do not push secrets to your distant Git repository !
- Build and package the application with signing enabled (release mode):
flutter build appbundle --build-number 1 --build-name 0.0.1 --release
Once the signed application is ready, it's time to upload it to Google Play Console: create a new release under "Internal tests" and drop your appbundle there, you should be able to continue with your brand new release.
Once your first release is accessible for internal tests, you can now upload automated releases through CI ! Yeah š
ā ļø Make sure you filled all necessary information on Google Play Console for internal tests (Dashboard will tell you what steps you need to complete first).
šš± iOS setup
Codemagic configuration for iOS deployment can be rather complicated.
To avoid handling certificates and provisioning profiles ourselves, we'll be using Fastlane and match for code signing, as explained in Prerequisites.
First make sure you use latest Fastlane version:
sudo gem install fastlane
Also create an empty Git repository called certificates
somewhere in your Gitlab/Github/Bitbucket. We can no run a few commands to setup codesigning for iOS
so make sure you are in ios
subdirectory:
cd ios/
Time to init match to point to our certificate repository:
match init
> Select git
> URL of the Git repo:
git@gitlab.com:your-company/your-app-group-folder/certificates.git
This should generate a Matchfile in .fastlane/Matchfile
with the following content:
git_url("git@gitlab.com:your-company/your-app-group-folder/certificates.git")
storage_mode("git")
type("development") # The default type, can be: appstore, adhoc, enterprise or development
# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
# username("user@fastlane.tools") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
ā ļø Use a SSH url for the repository (rather than the HTTPS url) as we will use an SSH key later to enable Codemagic fetching our repository.
Now we have to create ios/fatslane/Appfile
to fill out a few needed information for Fastlane (and match too):
app_identifier("com.company.awesomeApp") # The bundle identifier of your app
apple_id("apple-technical@your-company.com") # Your Apple email address for the technical account
team_id("123ABC1DE") # Developer Portal Team ID
itc_team_id("123456789") # App Store Connect Team ID
ā¹ļø To easily output these variables you can run fastlane produce
and you should get all these variables:
-
app_identifier
is your bundle id as created in Prerequisites - Config and can be found in your [Apple identifier](https://developer.apple.com/account/resources/identifiers/bundleId -
apple_id
is the technical account email address (e.g. apple-technical@your-company.com) -
team_id
is your Developer portal team ID and can be found in your Apple identifier -
itc_team_id
is an integer team id derived from aboveteam_id
Once all variables are set, we can use match to create new certificates and profiles.
Please make sure that the Apple account you set in apple_id
above has "App manager" permissions and not just "Developer" or you won't have permissions to create certificates and profiles.
Let's run the command to generate everything:
fastlane match appstore
Enter password for apple-technical@your-company.com
.
If the command executed successfully you should now have new, generated certificates and [profiles]https://developer.apple.com/account/resources/profiles/list).
Also these certificates should now be stored encrypted into your Git certificates repository.
ā ļø Don't forget to securely store ios keychain
and match
passwords and share them with your team if they need to regenerate certificates ! š
Now that you have generated profile and certificate, it's time to complete Codemagic workflow by setting all environment variables such as Fastlane environment variables:
-
BUILD_NAME
: The version that you wish to deploy, in semantic versioning notation (e.g. 1.2.3, or more generally X.Y.Z) -
IOS_APP_ID
: the app identifier as set in Certificates, Identifiers & Profiles.- Example: com.company.awesomeApp
-
APPLE_DEVELOPER_TEAM_ID
: Apple Developer Team Id (e.g. 123ABC4DE) -
FASTLANE_EMAIL
: Apple Developer account email (e.g. apple-technical@your-company.com) -
FASTLANE_PASSWORD
: Apple Developer account password -
MATCH_PASSWORD
: [Codemagic-encrypted] - Password used when generatingfastlane match appstore
above usingmatch
. -
SSH_KEY_FOR_FASTLANE_MATCH_BASE64
: [Codemagic-encrypted] - Base64 encoded private SSH key of Codemagic user for Gitlab. You can do the following to generate SSH keys :- Generate a new SSH key pair :
ssh-keygen -t ed25519 -C "SSH key description"
- Copy public key and add it under Gitlab's SSH keys for Codemagic user.
- Copy private key and encrypt it using Codemagic (see note below)
- Delete SSH key from your local computer
- If you ever need to see the key... you can't, just revoke the old one and generate a new pair !
- Generate a new SSH key pair :
-
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
: [Codemagic-encrypted] - Apple Application specific password as described in Fastlane documentation. You can follow these steps:- Go to Apple manage account page with your Apple account corresponding to
FASTLANE_EMAIL
. - Generate a new application specific password
- Encrypt the password using Codemagic (see note below)
- Go to Apple manage account page with your Apple account corresponding to
ā¹ļø You can run fastlane produce
to output some of these information (app_identifier, team_id, itc_team_id).
ā ļø Variables that are marked as [Codemagic-encrypted] must be encrypted using Codemagic user interface.
Go to any project
> āļø > Encrypt environment variable
and paste the variable you want to encrypt, and copy it back to codemagic.yaml
.
Keep the Encrypted(VAR)
around your variable !
iOS Project configuration
Just a few more things we need to setup:
- In
ios/Runner.xcodeproj/project.pbxproj
, set allDEVELOPMENT_TEAM
variables to your actual Apple Development team (must be the same asAPPLE_DEVELOPER_TEAM_ID
env var above). If development team variables do not appear inios/Runner.xcodeproj/project.pbxproj
you can setup manually by doing following steps:- Open Xcode
- Select Runner in left pannel
- In central panel select Targets > Runner
- Open "Signing & Capabilities" tab
- Under "All" sub-tab, disable automatic code siging and select your team from the appropriate field.
If no team is found you can also import a provisioning profile after downloading it from Apple profiles
-
In
ios/Runner/Runner.entitlements
(create the file if you don't have it), setup required Apple entitlements for your application.- If you don't use any entitlements, you should still declare it by leaving the file with an empty list:
<?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/> </plist>
Phew, you should be all set for iOS automated deployment š
Conclusion
As you can see setting up CI/CD automation for Flutter projects to go from the ground up to deployed beta versions of both Android and iOS applications requires a bit of patience, but in the end once you understand the various components interacting with each other, it's almost magic ! š§āāļø
My advice : take the time to set up CI/CD when your Flutter project starts, you will be grateful to have it all automated when comes the mandatory rush of your project š