⭐ Setup Fastlane + TestFlight + AppCenter for Android/iOS & Flutter
Please before following this documentation make sure you already have confirgurations described here : ⭐ Setup Mobile Development Environment
For example in this url : https://appcenter.ms/orgs/ToTo/applications
- owner_name : ToTo
- app_name: Your AppName
- app_platform: iOS (Obj-c/swift) / Android (Java)
- api_token: Go to https://appcenter.ms/orgs/YOUR_ORG/apps/YOUR_APP_NAME/settings/apitokens & generate one !
You are ready !
- Go to Identifiers
- Select : AppIds
- Select Type : App
- Fill Description : AppName
- Fill Bundle ID : (ex. com.example.toto)
- Choose App Capabilities (can be edited after)
- Then click Register
- Go to Certificates
- Choose : iOS Distribution (App Store and Ad Hoc)
- Optionnal : Generate a CSR
- Choose the generated CSR
- Click Validate
- Click download to download certificat
- Double click on the certificate to install it on your machine
- Go to Profiles
- Choose : AppStore
- Select AppId : (ex. com.example.toto)
- Choose distribution certificate
- Fill Provisionning Profile Name : (ex. toto-prod-pp)
curl -L https://get.rvm.io | bash -s stable
echo $"\nsource /Users/$(whoami)/.rvm/scripts/rvm\n" >> /Users/$(whoami)/.zshrc # or .bashrc
rvm install ruby-3.2.2
rvm use ruby-3.2.2
rvm --default use 3.2.2
gem install bundler
gem install fastlane -NV
then run :
bundle update
bundle install
bundle exec fastlane init
This will create folder fastlane with :
- Fastfile
- Appfile
default_platform(:ios)
platform :ios do
desc "Build & Deploy to TestFlight"
lane :public do |options|
## Update pubspec.yaml
## Work 50% of the time !!!
# Update Info plist Version
# increment_version_number(
# version_number: options[:version] # Set a specific version number
# )
## Work 50% of the time !!!
# auto increment buildNumber
# build_number = number_of_commits(all: true)
# increment_build_number(build_number: build_number)
# run this : chmod +x ./scripts/upgrade_buildNumber.sh
sh("../scripts/upgrade_buildNumber.sh", options[:version])
# Update Info plist Bundle ID
update_app_identifier(
xcodeproj: "Test.xcodeproj", # Optional path to xcodeproj, will use the first .xcodeproj if not set
plist_path: "./Test/Info.plist", # Path to info plist file, relative to xcodeproj
app_identifier: ENV["APP_ID"] # The App Identifier
)
# Update Info plist AppName
update_info_plist( # Change the Display Name of your app
plist_path: "./Test/Info.plist",
display_name: ENV["APP_NAME"]
)
# download and use certificate
match(type: "appstore", readonly: is_ci)
# Use gym to archive your app
gym(
silent: true,
output_directory: "./fastlane/builds",
scheme: ENV["SCHEME"]
)
# Use pilot to upload your app to testflight
pilot(
app_identifier: ENV["APP_ID"],
distribute_external: false,
)
end
desc "Build & Zip for Private Store"
lane :private do |options|
## some cleanup
sh "rm -rf builds/**.zip"
## Update pubspec.yaml
## Work 50% of the time !!!
# Update Info plist Version
# increment_version_number(
# version_number: options[:version] # Set a specific version number
# )
## Work 50% of the time !!!
# auto increment buildNumber
# build_number = number_of_commits(all: true)
# increment_build_number(build_number: build_number)
# run this : chmod +x ./scripts/upgrade_buildNumber.sh
sh("../scripts/upgrade_buildNumber.sh", options[:version])
# Update Info plist Bundle ID
update_app_identifier(
xcodeproj: "Test.xcodeproj", # Optional path to xcodeproj, will use the first .xcodeproj if not set
plist_path: "./Test/Info.plist", # Path to info plist file, relative to xcodeproj
app_identifier: ENV["APP_ID"] # The App Identifier
)
# Update Info plist AppName
update_info_plist( # Change the Display Name of your app
plist_path: "./Test/Info.plist",
display_name: ENV["APP_NAME"]
)
# Build Archive
xcodebuild(
archive: true,
archive_path: "./fastlane/builds/Test.xcarchive",
scheme: ENV["SCHEME"],
workspace: "Test.xcworkspace",
build_settings: {
"CODE_SIGNING_REQUIRED" => "NO",
"CODE_SIGN_IDENTITY" => "",
"CODE_SIGN_ENTITLEMENTS" => "",
"CODE_SIGNING_ALLOWED" => "NO"
}
)
# Clean Archive
sh "rm -rf builds/Test.xcarchive/dSYMs/"
sh "rm -rf builds/Test.xcarchive/SwiftSupport/"
# Zip Archive
zip(
path: "./fastlane/builds/Test.xcarchive",
output_path: "./fastlane/builds/test-"+ options[:version] +".xcarchive.zip"
)
end
desc "AppCenter Upload"
lane :appcenter do |options|
appcenter_upload(
api_token: "", # found in settings of user
owner_name: "", # found in the url : https://appcenter.ms/orgs/<owner_name>/applications
owner_type: "organization", # Default is user - set to organization for appcenter organizations
app_name: options[:app_name], # your app name
file: "./fastlane/app-release.ipa",
notify_testers: true # Set to false if you don't want to notify testers of your new release (default: `false`)
)
end
end
Geet team_id
This data can be found on Apple Account
Get itc_team_id
$ irb
irb> require "spaceship"
irb> Spaceship::Tunes.login("iTunesConnect_username", "iTunesConnect_password")
irb> Spaceship::Tunes.select_team
Don't forget to replace :
- iTunesConnect_username
- iTunesConnect_password
The result contains the itc_team_id
Get the FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
- Connect to AppleId
- Go to Security -> Generate Password for App -> set AppName -> copy the generated code "....-....-....-...."
- This code is the FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
#!/bin/bash
# Info Plist path
PATH_TO_INFOPLIST="YOUR_PATH"
INFOPLIST=$PATH_TO_INFOPLIST"/Info.plist"
# Type a script or drag a script file from your workspace to insert its path.
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
# Updrage BuildNumber with git build Numbe
oldversion=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFOPLIST"`
## Works 100% : Change BuildNumber
if [ "$buildNumber" != "$oldversion" ] ; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST"
fi
## Change Version
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $1" "$INFOPLIST"
# The bundle identifier of your app
app_identifier "com.exemple.toto"
# Apple Developer Account
apple_dev_portal_id "[email protected]"
# App Store Connect Account
itunes_connect_id "[email protected]"
# Developer Portal Team ID
## is found in the url : https://developer.apple.com/account/#/membership/<team_id>
team_id ""
# App Store Connect Team ID
## Check Step "Get itc_team_id"
itc_team_id ""
# Env for Pilot
ENV["FASTLANE_USER"] = "[email protected]"
ENV["FASTLANE_ITC_TEAM_ID"] = "<itc_team_id>"
## To setup 2 factor Auth for delivery
# Check Step "Get the FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"
ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = ""
# Specify the Trusted phone number to automatize sms verification step
## https://github.com/fastlane/fastlane/blob/master/spaceship/docs/Authentication.md#auto-select-sms-via-spaceship_2fa_sms_default_phone_number
### Go here and add your phone number as trusted phone, then fill the field : https://appleid.apple.com/
ENV["SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER"] = ""
# To Replace : .git storage for the certificate shared with hole team
git_url("https://toto/titi/tata.git")
storage_mode("git")
# The default type, can be: appstore, adhoc, enterprise or development
type("development")
# Your Apple Developer Portal username
username("[email protected]")
gem 'fastlane-plugin-appcenter'
source "https://rubygems.org"
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
- (ex. .env.dev for dev)
SCHEME="test-dev"
APP_ID="fr.exemple.test"
APP_NAME="Test DEV"
bundle update
bundle exec fastlane ios public version:"1.0.0" --env "dev" # ex. for dev
fastlane init
# choose 2 for testflight setup
This will create folder fastlane with :
- Fastfile
- Appfile
default_platform(:android)
platform :android do
desc "Deploy a new version to the Google Play"
lane :deploy do |options|
flutter_build
## To use this u need to generate : api.json !
## Check the Appfile
upload_to_play_store(track: 'beta')
end
desc "Deploy a new version to AppCenter"
lane :appcenter_bad do |options|
flutter_build
appcenter
end
desc "Build with fastlane with auto upgrade VersionCode"
lane :flutter_build do
# Return the number of commits in current git branch
build_number = number_of_commits()
Dir.chdir ".." do
sh("flutter", "packages", "get")
sh("flutter", "clean")
# sh("flutter", "build", "apk", "--build-number=#{build_number}")
# sh("flutter", "build", "appbundle", "--build-number=#{build_number}")
## TODO : Build for different BuildTypes !
sh("flutter", "build", "apk", "--release")
end
end
desc "AppCenter Upload"
lane :appcenter do |options|
appcenter_upload(
api_token: "", # set api Token from appcenter
owner_name: "", # Set App owner name
owner_type: "organization", # Default is user - set to organization for appcenter organizations
app_name: "", # your app name
file: "../build/app/outputs/flutter-apk/app-release.apk",
notify_testers: true,
app_platform: 'Java',
destinations: "", # Distribution group
destination_type: "group"
)
end
end
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("com.krausefx.app") # e.g.
gem 'fastlane-plugin-appcenter'
source "https://rubygems.org"
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
bundle update
bundle exec fastlane android appcenter_bad
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
.DS_Store
.Trashes
*.swp
*.lock
*~.nib
buildArchive/
DerivedData/
build/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
timeline.xctimeline
playground.xcworkspace
*.xccheckout
xcuserdata/
*.moved-aside
.build/
Pods/
Carthage/Checkouts
Carthage/Build
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**
fastlane/screenshots/**/*.png
fastlane/test_output
*.mobileprovision
*.cer
fastlane/*.cer
fastlane/*.mobileprovision
fastlane/builds/**
.history/**
*.p12
*.certSigningRequest
*.pdf
Mocks/**
**/android/.gradle
**/android/captures/
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
vendor/
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.classpath
.project
.settings/
# .vscode/
# Flutter repo-specific
/bin/cache/
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/Flutter-Debug.xcconfig
**/macos/Flutter/Flutter-Release.xcconfig
**/macos/Flutter/Flutter-Profile.xcconfig
# Coverage
coverage/
# Symbols
app.*.symbols
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
doc/*
.idea/*
#.env*
I will keep this updated in time !