Deploying iOS apps to early testers helps you get feedback for improvements or detect issues that came out from early on during the development. Firebase App Distribution, or App Distribution, is a service that helps you deploy your Flutter apps to testers.
This article focuses on automating deployments for Flutter (iOS) apps using ad-hoc releases in App Distribution. If youβre interested in implementing this for Android, you can read this.
Prerequisites
- An existing Flutter project; you can use our starter app or create one by running
flutter create my_app
- A Firebase account; you can create one here
- An Apple Developer Account ($99 / year) for the developer certificates and provisioning profiles
Firebase App Distribution
Deploying iOS apps is more complex than in Android. Unlike in Android, most of the time, you can create an android package (APK), and users can directly install and use them on their devices. But for iOS, itβs a different story. You can only install apps from the Apple Apps Store or signed ad-hoc releases from tools like App Distribution.
App Distribution and Apple Apps Store
App Distribution is an alternative service to deploy iOS apps to a limited number of testers to get early feedback or detect issues, and itβs not a complete replacement for the Apple Apps Store if you are planning to release your apps to the public.
Creating a Firebase Project
In the console, create a new project by tapping Add project.
Assign a project name and unique ID (optional).
You can also enable Google Analytics (Optional). For now, weβll disable this setting.
Authentication with Firebase CLI
Firebase CLI contains tools that allow you to manage, view, and deploy to Firebase. We will use Firebase CLI to authenticate CI later when deploying builds to Firebase projects.
Setting up Firebase CLI
There are at least two ways to set up Firebase CLI on your machine. Either by downloading the standalone binary or via the npm package manager.
If you already have npm, you can go ahead and install Firebase CLI using the package manager, as shown below:
npm install -g firebase-tools
Testing and logging in with Firebase CLI
Authenticate Firebase CLI using the email associated with your Firebase account:
firebase login
β Success! Logged in as joshua*****@gmail.com
For more information, see Firebase CLI reference.
Setting up App Distribution
App Distribution in Firebase is where deployed builds show up and where you can manage your app testers.
Setting up an iOS project
If you have an existing Flutter project that uses Firebase, please skip this step. Otherwise, do the following if you are using the starter app or have created a new project using flutter create
.
In the Firebase consoleβs sidebar, navigate to and open App Distribution.
Tap the iOS logo to create a new iOS project.
Then, enter the details of your iOS project. Optionally, you can download the GoogleService-Info.plist
file and put them to your root iOS project directory.
If you use the starter app, replace com.example.semaphoreciFlutterDemo
with your desired bundle identifier.
Setting up group testers
App Distribution allows you to configure multiple test groups depending on the build you deploy, for example, for internal or private beta builds.
Navigate to the App Distribution page and select the Testers & Groups tab.
Add a group named βtestersβ, then click Save.
Last, to deploy your build to the users, add their emails to the group.
fastlane
fastlane is an open-source platform with a wide range of tools and APIs for simplifying deployment for Android and iOS projects. We will use fastlane to automate our workflow, communicate with Firebase, and ultimately deploy the project.
Setting up Ruby
Ruby is a must-have to use fastlane in your project. Hereβs an exhaustive list of ways to install Ruby on your machine.
Setting up Bundler
Bundler and Gemfile are for defining dependencies in fastlane. Run the following command to install bundler:
gem install bundler
Setting up fastlane
If youβre on macOS and have Homebrew installed, run the following:
brew install fastlane
If you have gems installed on macOS/Windows/Linux, run the following:
sudo gem install fastlane
Setting up fastlane with your Flutter iOS project
Set up fastlane in the ios
directory of your Flutter project.
To initialize fastlane, run the following:
cd ios && fastlane init && cd -
- Select manual setup:
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
Then, wait for all the packages and dependencies to be installed properly.
You should now have a fastlane directory containing the Appfile
and Fastfile
.
Adding the Firebase App Distribution Plugin to Fastlane
We will need the plugin to upload the build artifacts to Firebase.
Install the fastlane plugin with the following command:
fastlane add_plugin firebase_app_distribution
Then, when asked to modify the Gemfile for the current path (ios), type y
.
Depending on your Ruby setup, you might be asked to enter your password and provide temporary root privileges when installing the dependency:
sudo fastlane add_plugin firebase_app_distribution
Setting up Fastfile with Firebase
Fastfile
Fastfile stores the configuration for automation using fastlane.
First, navigate to your Fastfile
directory and replace the default content with the following:
default_platform(:ios)
# Credentials (for later)
git_authorization = "<GIT_AUTHORIZATION>"
firebase_app_id = "<YOUR_FIREBASE_APP_ID>"
firebase_cli_token = "<YOUR_CLI_TOKEN>"
# Team
team_id = "<YOUR_TEAM_ID>"
platform :ios do
desc "Deploy iOS to Firebase"
lane :deploy do
# add actions here: https://docs.fastlane.tools/actions
end
end
deploy
here is the name of the fastlane workflow. To run the command in your terminal, run fastlane deploy
.
Next, below team_id variable, add the following:
# Keychains
keychain_user = "temp"
keychain_pass = "temp"
def 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
These are for creating temporary keychains and downloading developer certificates, and provisioning profiles on the CI.
Then, inside the deploy
lane, add the following:
platform :ios do
lane :deploy do
ensure_temp_keychain(keychain_user, keychain_pass)
gym(
scheme: "Runner",
archive_path: "./build/Runner.xcarchive",
export_method: "ad-hoc",
output_directory: "./build/Runner",
)
firebase_app_distribution(
app: firebase_app_id,
firebase_cli_token: firebase_cli_token,
release_notes: "Bug fixes and performance improvements",
groups: "internal",
)
delete_temp_keychain(keychain_user)
end
end
App ID
To get your App ID, click Project Overview, then Project Settings.
Next, select the current appβs Project Settings and copy your App ID.
Replace firebase_app_id
in your Fastfile
.
Generating Firebase CLI token
Earlier you authenticated Firebase CLI with your Firebase account; mainly for testing tooling installation. Now, we will authenticate and generate a token for CI.
To generate a token for CI run:
firebase login:ci
This should give a hashed string like this:
β Success! Use this token to login on a CI server:
1//0etIq08H_oIB5CgYIARAAGA4SNwF-L9IrgmGb2iadCGcAmqhzK_NH8F8zQBkbPrMS2qtWhMqUmpbVbYK4YzC_83-eHb8NgylEMUg
Replace firebase_cli_token
in your Fastfile
.
Setting up fastlane match
fastlane match, or match, simplifies the complex code signing for your iOS apps, and Semaphore has an in-depth documentation on how to deal with iOS code signing using fastlane match. I will explain the setup here succinctly.
Configuring match
Initialize match by running
fastlane match init
Next, select git
to store the developer certificates and provisioning profiles:
fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
>>> 1
Then, enter the URL of your git repository, for example: [https://github.com/joshuadeguzman/ios-certificates](https://github.com/joshuadeguzman/ios-certificates)
Finally, replace development
to adhoc
in your Matchfile
type("adhoc")
Generating certificates and profiles
Create ad-hoc certificates and profiles by running. When prompted, enter your Apple Developer Credentials.
fastlane match adhoc -a <YOUR_BUNDLE_IDENTIFIER>
...
All required keys, certificates and provisioning profiles are installed π
Use the match provisioning profile in your project settings using Xcode:
Downloading certificates and profiles in fastlane
In most cases, you should be able to clone your matchβs git private repository on your local machine. But in CI, we have to provide appropriate authorization to allow access.
Create your Github personal access token by following this.
Use the personal access token generate and replace the git_authorization
in yourFastfile
:
git_authorization = "joshuadeguzman:<YOUR_ACCESS_TOKEN>"
Last, add the match
command
platform :ios do
lane :deploy do
ensure_temp_keychain(keychain_user, keychain_pass)
# Add this
match(
type: "adhoc",
git_basic_authorization: git_authorization,
)
... (truncated)
end
end
Deploying to Firebase using fastlane locally
That’s it for Firebase and fastlane for now. Let’s test the Firebase deployment by running fastlane locally.
Ensure the project is in clean state:
flutter packages & clean
Then, build the Flutter iOS app:
flutter build ios β-no-codesign
To deploy your app, run the following:
cd ios && fastlane deploy && cd -
This should give you a success message for deploying your app to Firebase.
Great! Your latest iOS build should be displayed on App Distributionβs releases dashboard.
Deploying to Firebase using Semaphore
Before proceeding, I recommend reading core concepts that make Semaphore intuitive and powerful. I also explained the Visual Builder for iOS continuous integration in this article.
Setting up a Semaphore project for your app
Log in with your Semaphore account, then create a new project.
Choose the repository for your project, then click Continue to workflow set up.
Then for the workflow set up, click Customize.
Setting up blocks to integrate and build your apps
Configuring the workflow pipeline
Weβll be using a Mac Based Virtual Machine environment with a macos-xcode13 image.
Setting up your blocks for Continuous Integration (CI)
Create a block named Install Dependencies, then add a job Install and cache Flutter:
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
)
Then, add a new block named Lint, then add jobs Format and Analyze:
Format
flutter format --set-exit-if-changed .
Analyze
flutter analyze .
Prologue
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get
Finally, add a block to run your Flutter tests.
Run unit and widget tests
flutter test test
Prologue
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
flutter pub get
Optionally, you can test your workflow by clicking Run the workflow.
Configuring promotions to deploy your apps
Setting up a new promotion
Promotions help us create conditions around our workflows and whether or not we have to run another workflow or set of workflows. Also, they can be triggered manually or automatically.
After configuring the visual builder, we can merge the set-up-semaphore branch to the default branch for continuous deployment to Firebase using automatic promotions. In our case, the Deploy to Firebase promotion gets triggered once the condition "branch = 'master' AND result = 'passed'
is satisfied by the workflow run.
Configuring the deployment pipeline
Similar to the Main pipeline, we will use a Mac Based Virtual Machine environment with a macos-xcode13 image.
Next, in this pipeline, you will also need a Install Dependencies block set up with a job Install and cache Flutter:
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
Last, create a block named Deploy to Firebase with a job Run Fastlane:
checkout
cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
sh bump_version.sh
flutter build ios --no-codesign
cd ios
bundle install
cache store
bundle exec fastlane deploy
This block is responsible for executing your deploy
lane in fastlane.
Click Run the workflow and commit the changes.
Awesome, your workflow should be ready for automatic deployments.
Testing continuous deployment
To check automatic promotions and deployments, we should merge it to the working branch to master:
git fetch --all
git merge origin/set-up-semaphore
git push origin master
Now, push the changes to trigger the build.
Conclusion
Setting up a continuous deployment pipeline for iOS takes more time than Android, but itβs worth the investment. Creating new releases every time a developer merges a pull request using this approach saves you half the time if youβre doing all the steps mentioned above manually, especially dealing code-signing for your teams.