Automate Flutter App Deployment on iOS to TestFlight using Fastlane and Semaphore

Prerequisites

Preparing a Flutter (iOS) App

TestFlight

Photo by TestFlight.

Creating a new bundle identifier

Creating a new bundle identifier

Creating a new app

Assigning the bundle identifier

Setting up fastlane

Installation

Setting up fastlane for iOS

cd ios && fastlane init && cd
What would you like to use fastlane for?
1. 📸 Automate screenshots
2. 👩‍✈️ Automate beta distribution to TestFlight
3. 🚀 Automate App Store distribution
4. 🛠 Manual setup - manually setup your project to automate your tasks
>>> 4

Setting up fastlane match

fastlane match init
fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
type("appstore")

Generating fastlane match credentials

app_identifier(["com.yourapp.example"])
fastlane match appstore
fastlane match appstore -a com.yourapp.example

Setting environment variables

example_value = ENV["EXAMPLE_VALUE"]
sem create secret semaphore-flutter2-env \
-e MATCH_GIT_URL="<YOUR_MATCH_GIT_URL>" \
-e MATCH_PASSWORD="<YOUR_MATCH_PASSWORD>" \
-e MATCH_GIT_AUTHORIZATION="<YOUR_GIT_AUTHORIZTION_TOKEN>" \
-e FASTLANE_USER="<YOUR_APPLE_ID_EMAIL>" \
-e FASTLANE_PASSWORD="<YOUR_APPLE_ID_PASSWORD>"\
-e FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD="<YOUR_APPLE_APP_SPECIFIC_PASSWORD"
git_authorization = ENV["MATCH_GIT_AUTHORIZATION"]
team_id = ENV["TEAM_ID"]
app_id = ENV["APP_ID"]
app_identifier = ENV["APP_IDENTIFIER"]
provisioning_profile_specifier = ENV["PROVISIONING_PROFILES_SPECIFIER"]
temp_keychain_user = "temp"
temp_keychain_password = "temp"

Creating fastlane deploy lane

# This is where the environment variables are located(truncated)# Add the followingdef delete_temp_keychain(name)
delete_keychain(
name: name
) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end
def create_temp_keychain(name, password)
create_keychain(
name: name,
password: password,
unlock: false,
timeout: 0
)
end
def ensure_temp_keychain(name, password)
delete_temp_keychain(name)
create_temp_keychain(name, password)
end
platform :ios do
lane :deploy do
# Step 1 - Create keychains
keychain_name = temp_keychain_user
keychain_password = temp_keychain_password
ensure_temp_keychain(keychain_name, keychain_password)
# Step 2 - Download provisioning profiles and certificates
match(
type: 'appstore',
app_identifier: app_identifier,
git_basic_authorization: Base64.strict_encode64(git_authorization),
readonly: true,
keychain_name: keychain_name,
keychain_password: keychain_password
)
# Step 3 - Build the project
gym(
configuration: "Release",
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store",
export_options: {
provisioningProfiles: {
app_id => provisioning_profile_specifier,
}
}
)
# Step 4 - Upload the project
pilot(
apple_id: "#{app_id}",
app_identifier: "#{app_identifier}",
skip_waiting_for_build_processing: true,
skip_submission: true,
distribute_external: false,
notify_external_testers: false,
ipa: "./Runner.ipa"
)
# Step 5 - Delete temporary keychains
delete_temp_keychain(keychain_name)
end
end

Deploying to Semaphore

Semaphore

Creating your Semaphore project

Setting up continuous integration pipeline

checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get
cache store flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages /root/.pub-cache
flutter format --set-exit-if-changed .
flutter analyze .
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get
flutter test test
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get

Setting up continuous deployment pipeline

checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get
cache store flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages /root/.pub-cache
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter build ios --no-codesign
cd ios
bundle install
cache store
bundle exec fastlane deploy
git fetch --all
git merge origin/set-up-semaphore
git push origin master

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store