We use fastlane
for our flutter apps, for ios and android builds, it give us a common language to define the setup and reuse code for:
- Generating a changelog based on our changelog style
- Validate code style, formatting and lint
- Unit testing
- Verify that the project autogenerated code is been built fine.
- Verify that the project builds fine for each platform.
- Deploy to firebase app distribution for testing
- Deploying to each app store
There are some tricks to be able to do it:
- Create a parent
fastfile
where you have the reusable logic and one for each platformios/fastfile
andandroid/fastfile
. - Have an easy way to run tasks from the root of the project, our idea was to create a lane
sh_on_root
to run all flutter commands from each platformfastfile
. - Create a
Gemfile
for each platform and define.ruby-version
file to have a defined environment to run, helping us to avoid problems on CI server requiring some specific ruby version to run fastlane in there.
Another thing we noticed is that using --no-pub --suppress-analytics
params from flutter commands saved us some time on CI if you want to try that's a good start.
I will show an example of some usefull lanes and some workflows for them, you should think about your needs and call the lanes you need for each step but try to reuse for each platform some tasks having the parent fastfile
file.
Show me the setup
- Create the
Gemfile
for each platform android/Gemfile
ios/Gemfile
Contents:
source "https://rubygems.org"
gem 'cocoapods'
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
- Create the
.ruby-version
so the CI (in our case Bitrise) have a clue of which ruby version should be used ios/.ruby-version
android/.ruby-version
Contents:
2.6.5
- Create 3 fastlane files, 1 to be reused by each platform, and 1 for each platform (check that from each platform fastlane we import the parent one with
import "../../scripts/Fastfile"
)
-
fastfile
:
opt_out_usage
# Have an easy way to get the root of the project
def root_path
Dir.pwd.sub(/.*\Kfastlane/, '').sub(/.*\Kandroid/, '').sub(/.*\Kios/, '').sub(/.*\K\/\//, '')
end
# Have an easy way to run flutter tasks on the root of the project
lane :sh_on_root do |options|
command = options[:command]
sh("cd #{root_path} && #{command}")
end
# Tasks to be reused on each platform flow
lane :fetch_dependencies do
sh_on_root(command: "flutter pub get --suppress-analytics")
end
# Tasks to be reused on each platform flow
lane :build_autogenerated_code do
sh_on_root(command: "flutter pub run intl_utils:generate && flutter pub run build_runner build --delete-conflicting-outputs")
end
# Tasks to be reused on each platform flow
lane :lint do
sh_on_root(command: "flutter format --suppress-analytics --set-exit-if-changed -n lib/main.dart lib/src/ test/")
end
# Tasks to be reused on each platform flow
lane :test do |options|
sh_on_root(command: "flutter test --no-pub --coverage --suppress-analytics")
end
-
ios/fastfile
:
import "../../scripts/Fastfile"
default_platform(:ios)
platform :ios do
# Updates XCode project settings to use a different code signing based on method
private_lane :archive do |options|
method = options[:method]
profile_name = method == 'ad-hoc' ? "Distribution - Staging (adhoc)" : "Production"
update_code_signing_settings(profile_name: profile_name)
build_app(export_method: method)
end
private_lane :authenticate_apple_store do
p8_path = path_for_secret("your .p8 file path")
app_store_connect_api_key(
key_id: "your key id",
issuer_id: "your used id",
key_filepath: p8_path,
duration: 1200, # optional
in_house: false,
)
end
lane :build do |options|
# Reuse parent fastfile tasks
fetch_dependencies
build_autogenerated_code
sign_enabled = options[:sign_enabled] || false
sign_param = sign_enabled ? '' : '--no-codesign'
config_only = options[:config_only] || false
config_param = config_only ? '--config-only' : ''
sh_on_root(command: "flutter build ios --no-pub --suppress-analytics --release #{sign_param} #{config_param}")
end
lane :deploy_staging do
build(sign_enabled: true)
archive(method: "ad-hoc")
upload_symbols_to_crashlytics(dsym_path: "your.app.dSYM.zip")
firebase_app_distribution(
app: "yours app id from firebase",
ipa_path: "your.ipa",
groups: "developers, staging",
release_notes: changelog
)
end
lane :deploy_staging_testflight do
build(sign_enabled: true)
archive(method: "app-store")
authenticate_apple_store
# Reuse parent fastfile tasks
test
upload_to_testflight(
ipa: "your.ipa",
reject_build_waiting_for_review: true,
skip_waiting_for_build_processing: false,
distribute_external: true,
notify_external_testers: true,
groups: "Your testers"
)
end
lane :deploy_production do
# All certificates and .p8 file should be fine on runnning machine
build(sign_enabled: true, config_only: true)
archive(method: "app-store")
authenticate_apple_store
# Reuse parent fastfile tasks
test
deliver(
ipa: "your.ipa",
skip_metadata: true,
skip_screenshots: true,
submit_for_review: false,
force: false,
automatic_release: false,
submission_information: {
add_id_info_serves_ads: false,
add_id_info_uses_idfa: false,
export_compliance_uses_encryption: false,
},
precheck_include_in_app_purchases: false,
)
end
end
-
android/fastfile
import "../../scripts/Fastfile"
default_platform(:android)
platform :android do
private_lane :build_apk do
# Reuse parent fastfile tasks
fetch_dependencies
build_autogenerated_code
sh_on_root(command: "flutter build apk --release")
end
lane :build do
# Reuse parent fastfile tasks
fetch_dependencies
build_autogenerated_code
sh_on_root(command: "flutter build appbundle --no-pub --release --suppress-analytics")
end
lane :deploy_staging do
build_apk
# Reuse parent fastfile tasks
test
firebase_app_distribution(
app: "your app id",
groups: "your testers",
android_artifact_type: "APK",
android_artifact_path: "#{root_path}/build/app/outputs/flutter-apk/app-release.apk",
firebase_cli_path: "/usr/local/bin/firebase"
)
end
lane :deploy_production do
build
# Reuse parent fastfile tasks
test
supply(
track: 'beta',
aab: "../build/app/outputs/bundle/release/app-release.aab",
json_key: path_for_secret("your play store.json"),
skip_upload_apk: true, # Upload the aab instead of apk
skip_upload_metadata: true,
skip_upload_changelogs: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end
Top comments (5)
this article help me alot.
any guide how to use this on github action? also how to use when we have dart--define as json file
what's dart-define? in the GitHub action you can install ruby and ruby gems and just run it
Hello, This article is well written!
But I have a question about it: what's the content of "scripts/cp_env.sh" in the ios/fastfile ?
thanks!
what it shoul do is to copy all environment variables into a .env file, I was using this package to handle environment variables and so it replaces the local ones with the ones defined on CI pub.dev/packages/flutter_dotenv
if you are interested in Flutter, then read this article - dev.to/pablonax/free-flutter-templ...
Some comments have been hidden by the post's author - find out more