Outline
If you develop the app by Flutter, you’ll deploy the app to make users use it. The Flutter official documentation introduces how to deploy the Flutter app. So, you can deploy the app by referring to it.
- Android deployment: https://docs.flutter.dev/deployment/android
- iOS deployment: https://docs.flutter.dev/deployment/ios
Fastlane
is a tool to help you deploy a native app or hybrid app(Flutter, React Native, etc).
- Fastlane: https://fastlane.tools/
In this blog post, I will show you how to deploy the Flutter app with Fastlane
.
Fastlane
fastlane is the easiest way to automate beta deployments and releases for your iOS and Android apps. It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.
In this blog post, we will assume that screenshots and app store registration information are already registered, and will only cover the part that automates deployment for testing and release.
If you want to know other information like how to create the screenshots and app store registration information, see the following official site link.
- Official site: Fastlane
On the Flutter official site, you can see how to use Fastlane
for CI/CD
, so it’s also useful to refer to it.
- Official site: https://docs.flutter.dev/deployment/cd
Install Fastlane
To deploy the app developed with Flutter via Fastlane
, you need to install Fastlane
first. Execute the following command to install Fastlane
.
# Using RubyGems
sudo gem install fastlane -NV
# Alternatively using Homebrew
brew cask install fastlane
The official site introduces how to install with Homebrew
and how to install with RubyGems
.
In my case, I tried to install it with Homebrew first, but I failed to deploy the app when I tested it. So, I recommend you install it via RubyGems. When you have any issues with Homebrew, try to use RubyGems to install it again.
iOS
Let’s see how to deploy the Flutter app to iOS via Fastlane.
initialize Fastlane for iOS
Execute the following commands to initialize Fastlane for iOS.
cd ios
fastlane init
After executing the commands, you can see the following screen.
In this blog post, I will introduce how to deploy the Flutter app to Testflight and App store. So, select 2
or 3
to go to the next.
2. Automate beta distribution to TestFlight
3. Automate App Store distribution
In here, I selected 2
option.
After selecting 2
, you can see the screen to select the iOS project. We will deploy the Flutter app, so select 2
to go to the next step.
When you select 2
, you can see the screen to login Apple like the above. To deploy the app to iOS, you should login with Apple store connect
account.
I already logged in with Fastlane, so the screen above is shown up. If you’re the first time to configure Fastlane, you will see the two-factor authentication procedure.
In addition to the above screen, you can see Continue by pressing Enter
several times. Press the Enter
key to finish the configuration.
Fastlane folders and files for iOS
After the configuration, you can see the folders and files are created in the ios folder of the Flutter project like the following.
|- fastlane
| |- Appfile
| |- Fastfile
|- Gemfile
|- Gemfile.lock
Let’s see the details about the folders and files.
- fastlane folder: The folder has Fastlane configuration and execution files.
- Gemfile, Gemfile.lock: Fastlane is developed by Ruby. These files manage the dependencies of Ruby for Fastlane.
If you uncomment the fastlane/Appfile
file that contains the settings to run Fastlane, it looks like the following.
app_identifier("io.github.dev-yakuza.kumoncho")
apple_id("[email protected]")
itc_team_id("119423059")
team_id("WFDJCJXQZ6")
The Appfile
file is the Fastlane configuration file for iOS deployment automation. The file is simple, so I omit the details.
Next, if you uncomment the fastlane/Fastfile
file that is the execution file to deploy the app, it looks like the following.
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "kumoncho.xcodeproj")
build_app(workspace: "kumoncho.xcworkspace", scheme: "kumoncho")
upload_to_testflight
end
end
You can execute it by executing the Fastlane command like the following.
# cd ios
fastlane beta
If you execute the Fastlane, the build number of iOS is increased(increment_build_number
), the app is built(build_app
), and the app is deployed to Testflight(upload_to_testflight
).
Modify iOS execution file
You can’t fully automate deployment with the fastlane file provided by default. So, you need to modify the fastlane/Fastfile
like the following.
# frozen_string_literal: true
default_platform(:ios)
platform :ios do
def updateVersion(options)
if options[:version]
version = options[:version]
else
version = prompt(text: "Enter the version type or specific version\n(major, minor, patch or 1.0.0): ")
end
re = /\d+.\d+.\d+/
versionNum = version[re, 0]
if versionNum
increment_version_number(
version_number: versionNum
)
elsif version == 'major' || version == 'minor' || version == 'patch'
increment_version_number(
bump_type: version
)
else
UI.user_error!('[ERROR] Wrong version!!!!!!')
end
end
def certificate(options)
if options[:type] == 'github'
create_keychain(
name: 'ios_app_keychain',
password: '****************',
timeout: 1800,
default_keychain: true,
unlock: true,
lock_when_sleeps: false
)
import_certificate(
certificate_path: 'distribution.p12',
certificate_password: '****************',
keychain_name: 'ios_app_keychain',
keychain_password: '****************'
)
end
install_provisioning_profile(path: 'distribution.mobileprovision')
update_project_provisioning(
xcodeproj: 'Runner.xcodeproj',
target_filter: 'github',
profile: 'distribution.mobileprovision',
build_configuration: 'Release'
)
api_key = app_store_connect_api_key(
key_id: '**************',
issuer_id: '***********************',
key_filepath: 'distribution.p8'
)
api_key
end
desc 'Update version'
lane :version do |options|
updateVersion(options)
increment_build_number(xcodeproj: 'Runner.xcodeproj')
end
desc 'Submit review only'
lane :submit_review do |_options|
upload_to_app_store(
submit_for_review: true,
automatic_release: true,
force: true,
skip_metadata: true,
skip_screenshots: true,
skip_binary_upload: true
)
end
desc 'Push a new beta build to TestFlight'
lane :beta do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_testflight(api_key: api_key)
end
desc 'Push a new release build to the App Store'
lane :release do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_app_store(
force: true,
reject_if_possible: true,
skip_metadata: false,
skip_screenshots: true,
languages: ['en-US', 'ja','ko'],
release_notes: {
"default" => "bug fixed",
"en-US" => "bug fixed",
"ja" => "バグ修正",
"ko" => "버그 수정"
},
submit_for_review: true,
precheck_include_in_app_purchases: false,
automatic_release: true,
submission_information: {
add_id_info_uses_idfa: false,
export_compliance_encryption_updated: false,
export_compliance_uses_encryption: false
},
api_key: api_key
)
end
end
사용자가 입력한 버전 상황에 맞게, 버전을 수정하도록 하는 함수를 정의하였습니다.
platform :ios do
def updateVersion(options)
...
end
...
desc 'Update version'
lane :version do |options|
updateVersion(options)
increment_build_number(xcodeproj: 'Runner.xcodeproj')
end
...
end
I’ve created a Fastlane command called version
that allows you update the version of the app. You can execute the following command to update the app version.
# fastlane version version:1.0.0
# fastlane version version:major
# fastlane version version:minor
fastlane version version:patch
If you don’t insert the version, the command will wait for the user input.
def updateVersion(options)
if options[:version]
version = options[:version]
else
version = prompt(text: "Enter the version type or specific version\n(major, minor, patch or 1.0.0): ")
end
...
You need Certification, Provisioning file and App store connect API key to deploy the app with Fastlane.
After preparing the files, you can use the certificate
function to register them and return the API
key where you want to use it.
# frozen_string_literal: true
default_platform(:ios)
platform :ios do
...
def certificate(options)
if options[:type] == 'github'
create_keychain(
name: 'ios_app_keychain',
password: '****************',
timeout: 1800,
default_keychain: true,
unlock: true,
lock_when_sleeps: false
)
import_certificate(
certificate_path: 'distribution.p12',
certificate_password: '****************',
keychain_name: 'ios_app_keychain',
keychain_password: '****************'
)
end
install_provisioning_profile(path: 'distribution.mobileprovision')
update_project_provisioning(
xcodeproj: 'Runner.xcodeproj',
target_filter: 'github',
profile: 'distribution.mobileprovision',
build_configuration: 'Release'
)
api_key = app_store_connect_api_key(
key_id: '**************',
issuer_id: '***********************',
key_filepath: 'distribution.p8'
)
api_key
end
...
end
The API
key that is made with the certificate
function is used for deploying the app to Testflight or App store like the following.
# frozen_string_literal: true
default_platform(:ios)
platform :ios do
...
desc 'Push a new beta build to TestFlight'
lane :beta do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_testflight(api_key: api_key)
end
desc 'Push a new release build to the App Store'
lane :release do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_app_store(
force: true,
reject_if_possible: true,
skip_metadata: false,
skip_screenshots: true,
languages: ['en-US', 'ja','ko'],
release_notes: {
"default" => "bug fixed",
"en-US" => "bug fixed",
"ja" => "バグ修正",
"ko" => "버그 수정"
},
submit_for_review: true,
precheck_include_in_app_purchases: false,
automatic_release: true,
submission_information: {
add_id_info_uses_idfa: false,
export_compliance_encryption_updated: false,
export_compliance_uses_encryption: false
},
api_key: api_key
)
end
end
Testflight용으로 배포하는 스크립트를 보면, 인증서를 등록한 후, 앱을 빌드하고, Fastlane가 제공하는 upload_to_testflight
함수를 사용하여 배포하는 것을 확인할 수 있습니다.
...
platform :ios do
...
desc 'Push a new beta build to TestFlight'
lane :beta do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_testflight(api_key: api_key)
end
...
end
The code for App store also registers the certification like the code for Testflight, and then builds the app. After then, the upload_to_app_store
function will be called for deploying the app.
...
platform :ios do
...
desc 'Push a new release build to the App Store'
lane :release do |options|
api_key = certificate(options)
build_app(
workspace: 'Runner.xcworkspace',
scheme: 'Runner',
)
upload_to_app_store(
force: true,
reject_if_possible: true,
skip_metadata: false,
skip_screenshots: true,
languages: ['en-US', 'ja','ko'],
release_notes: {
"default" => "bug fixed",
"en-US" => "bug fixed",
"ja" => "バグ修正",
"ko" => "버그 수정"
},
submit_for_review: true,
precheck_include_in_app_purchases: false,
automatic_release: true,
submission_information: {
add_id_info_uses_idfa: false,
export_compliance_encryption_updated: false,
export_compliance_uses_encryption: false
},
api_key: api_key
)
end
end
The options of the upload_to_app_store
functions are like the following.
- force: The HTML report created by Fastlane won’t be generated.
- reject_if_possible: If there is a version pending review, it will be canceled.
- skip_metadata: You can decide whether to register the App store information or not. In the deployment automation, you need to write the release notes, so
false
should be set. - skip_screenshots: I already registered the screenshots, so I don’t need to upload the screenshots again.
- languages: You can set the localization of the registered app.
ar-SA, ca, cs, da, de-DE, el, en-AU, en-CA, en-GB, en-US, es-ES, es-MX, fi, fr-CA, fr-FR, he, hi, hr, hu, id, it, ja, ko, ms, nl-NL, no, pl, pt-BR, pt-PT, ro, ru, sk, sv, th, tr, uk, vi, zh-Hans, zh-Hant
can be used. If you wnat to know more details, see the offical site. (Official site) - release_notes: When you deploy the app to iOS, you need to write the release notes. My app supports the 3-languages, so the default and 3-languages of the release notes are configured here.
- submit_for_review: The app will be submitted to review.
- automatic_release: After reviewing, the app will be deployed automatically. If this option is not set, the developer will deployt the app after reviewing.
- submission_information: This option is for the submission information like the encryption and the app including the ads.
There are many other options. You can see the details on the offical site.
- Official site: https://docs.fastlane.tools/actions/upload_to_app_store/
- Example: submission_information
Execute Fastlane to deploy Testflight for iOS
We’re ready to deploy the app via Fastlane for iOS. Next, let’s deploy the app using Fastlane.
Execute the Fastlane command below to deploy the Flutter app to Testflight.
# cd ios
fastlane beta version:patch
It takes an extremely long time for the deployment to complete. When the deployment is done, you can see the screen like the following.
Of course, you can see the app is deployed well to Testflight on App store connect.
Execute Fastlane to deploy App store for iOS
Next, execute the Fastlane command below to deploy the Flutter app to App store.
# cd ios
fastlane release version:patch
When the deployment is done, you can see the screen like following.
Also, you can see the app is deployed well on App store connect.
Android
Now, let’s automate the deployment of Android apps developed with Flutter using Fastlane.
Create Service Account for API access
When you deploy the app with Fastlane for Android, you need to create Google Developer Service Account
to use the Google API.
To create Google Developer Service Account, click the link below to go to Google Play Console.
- Google Play Console: https://play.google.com/apps/publish/
When you go to Google Play Console, you can see the screen like the following.
Click Settings
on the left menu. And the, click the API access
menu on the bottom of Developer account
.
When you see the screen above, click the CREATE NEW PROJECT
button to create a new project.
After creating the new project, you can see the screen above. Click the CREATE SERVICE ACCOUNT
button on the bottom of the screen, and you can see the screen like the following.
Click the Google API Console
link on the screen above. after clicking, you can see the screen like the following.
click the CREATE SERVICE ACCOUNT
button on the top of the screen. After clicking, you can see the screen to create a new Service account.
On the screen above, insert a name to Service account name
, and click the CREATE
button to create the Service account. (In my case, I inserted google-play-fastlane-deployment to Service account name.)
When you see the screen above, click Role
and then, search and select Service Account User
. After selecting Service Account User, click the CONTINUE
button on the bottom of the screen to go to the next step.
When you see the screen above, select CREATE KEY
on the bottom, and click the CREATE
button with selecting JSON
to create the key.
When you click the CREATE button to create key, the JSON file is downloaded automatically. Copy this file to the android
folder in the Flutter project. And, click the DONE
button to create Service Account.
After then, go bck to the original screen and click the DONE
button on the right bottom of the screen to finish the Service Account creation.
And then, you can see the screen like the following unlike before.
Next, click the GRANT ACCESS
button on the right bottom of the screen to authorize the account.
When you see the screen above, scroll down and click the ADD USER
button to register the user.
Initialize Fastlane for Android
Next, let’s create Fastlane for Android. Execute the command below to create Fastlane for Android.
cd android
fastlane init
After executing the command above, you can see the screen like the following.
Insert the Android project Package Name
. (ex> io.github.dev.yakuza.kumoncho) And then, you can see the screen asking you to enter the path of the JSON file as the following.
When you created Service Account, you copied the JSON file to the android
folder. Set this file path. (ex> app-xxx.json
)
Next, when deploying Android, it asks if you want to download the registered the store information (metadata). I already deployed the app, so I don’t need to update the store information. So, I inserted n
to skip downloading the store information.
After the screen above, you can see several times of the Continue by pressing Enter
screen. Press Enter key to go procedure.
Fastlane folders and files for Android
After finishing the configuration, you can see the folders and files under the android folder of the Flutter project like the following.
|- fastlane
| |- Appfile
| |- Fastfile
|- Gemfile
|- Gemfile.lock
Let’s see the details of each folder and file.
- fastlane folder: The folder has Fastlane configuration and execution files.
- Gemfile, Gemfile.lock: Fastlane is developed by Ruby. These files manage the dependencies of Ruby for Fastlane.
If you uncomment the fastlane/Appfile
file that contains the settings to run Fastlane, it looks like the following.
json_key_file("api-xxx.json")
package_name("io.github.dev.yakuza.kumoncho")
You can see the Package Name and the JSON file path that we’ve configured above. Next, if you uncomment the fastlane/Fastfile
file that is the execution file to deploy the app, it looks like the following.
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end
Unlike iOS, you can see two lanes named beta
and deploy
. Of course, you can execute the commands below to execute the Fastlane above.
# cd android
fastlane beta
fastlane deploy
However, you can’t automate deployment with this script, so you need to modify Fastfile.
Modify execution file for Android
You can’t automate deployment with the Fastlane file provided basically. So, we need to modify the fastlane/Fastfile
file as follows to automate deployment for Android.
# frozen_string_literal: true
default_platform(:android)
platform :android do
def increment_version_code
path = '../app/build.gradle'
re = /versionCode\s+(\d+)/
s = File.read(path)
versionCode = s[re, 1].to_i
s[re, 1] = (versionCode + 1).to_s
f = File.new(path, 'w')
f.write(s)
f.close
end
def increment_version_number(bump_type: nil, version_number: nil)
path = '../app/build.gradle'
re = /versionName\s+("\d+.\d+.\d+")/
s = File.read(path)
versionName = s[re, 1].gsub!('"', '').split('.')
major = versionName[0].to_i
minor = versionName[1].to_i
patch = versionName[2].to_i
if bump_type == 'major'
major += 1
minor = 0
patch = 0
elsif bump_type == 'minor'
minor += 1
patch = 0
elsif bump_type == 'patch'
patch += 1
end
s[re, 1] = if version_number
"\"#{version_number}\""
else
"\"#{major}.#{minor}.#{patch}\""
end
f = File.new(path, 'w')
f.write(s)
f.close
increment_version_code
end
def updateVersion(options)
version = options[:version] || prompt(text: "Enter the version type or specific version\n(major, minor, patch or 1.0.0): ")
re = /\d+.\d+.\d+/
versionNum = version[re, 0]
if versionNum
increment_version_number(
version_number: versionNum
)
elsif %w[major minor patch].include?(version)
increment_version_number(
bump_type: version
)
else
UI.user_error!('[ERROR] Wrong version!!!!!!')
end
end
desc 'Update version'
lane :version do |options|
updateVersion(options)
end
desc 'Submit a new Beta'
lane :beta do |_options|
gradle(task: 'clean bundleRelease')
upload_to_play_store(
skip_upload_metadata: true,
skip_upload_screenshots: true,
skip_upload_images: true,
skip_upload_apk: true,
track: 'internal',
aab: '../build/app/outputs/bundle/release/app-release.aab'
)
end
desc 'Deploy a new version to the Google Play'
lane :release do |_options|
gradle(task: 'clean bundleRelease')
upload_to_play_store(
skip_upload_metadata: true,
skip_upload_screenshots: true,
skip_upload_images: true,
skip_upload_apk: true,
aab: '../build/app/outputs/bundle/release/app-release.aab'
)
end
end
Let’s see the details of the file. There is no updating the app version feature in Android, unlike iOS. (I couldn’t find it, if you know, please give me a feedback.) So, I implement updating the versionCode and versionName of Android.
platform :android do
def increment_version_code
...
end
def increment_version_number(bump_type: nil, version_number: nil)
...
end
def updateVersion(options)
...
end
...
end
The updateVersion
function to update the version has been described in iOS
, so the detailed description will be omitted.
Fastlane for Android to deploy for internal test
Let’s see the details of the Fastlane code for the internal test.
platform :android do
...
desc 'Submit a new Beta'
lane :beta do |_options|
gradle(task: 'clean bundleRelease')
upload_to_play_store(
skip_upload_metadata: true,
skip_upload_screenshots: true,
skip_upload_images: true,
skip_upload_apk: true,
track: 'internal',
aab: '../build/app/outputs/bundle/release/app-release.aab'
)
end
...
end
To build the app for Android, the clean
and bundleRelease
(not assembleRelease
) commands of Gradle is used. Also, the track: 'internal'
option is configured into the upload_to_play_store
function to deploy the app to the internal test.
Release notes(change log) is not required on Android, unlike iOS. So, set all options of registering the store option to skip.
Lastly, we’ll use the bundleRelease
command to make the aab file and upload it, so set true
to the skip_upload_apk
option.
Fastlane for Android to deploy for Google Play
Next, let’s see the details of the Fastlane code for Google Play.
...
platform :android do
...
desc 'Deploy a new version to the Google Play'
lane :release do |_options|
gradle(task: 'clean bundleRelease')
upload_to_play_store(
skip_upload_metadata: true,
skip_upload_screenshots: true,
skip_upload_images: true,
skip_upload_apk: true,
aab: '../build/app/outputs/bundle/release/app-release.aab'
)
end
end
I change the name from lane :deploy
to lane :release
to make it as same as iOS. The code is the same as the internal test except the track
option is not configured to the upload_to_play_store
function, so I omit the description of the code.
Execute Fastlane for Android
We’re ready to automate deploy for Android via Fastlane. Now, let’s execute the Fastlane to deploy.
You can deploy the app to the internal test by executing the command below.
# cd android
fastlane beta version:patch
It takes an extremely long time for the deployment to complete. When the deployment is done, you can see the screen like the following.
Of course, you can see the app is deployed well to the internal test on Play store console.
Next, execute the command below to deploy the app to the production.
# cd android
fastlane release version:patch
When the deployment is done, you can see the screen like the following.
Also, you can see the app is deployed well on Google Play store.
gitignore
When you deploy the app with Fastlane, some files are generated. And, you don’t need to manage some of them on Git. So, open the .gitignore
file and modify it like the following.
...
# fastlane
ios/*.mobileprovision
ios/*.cer
ios/*.dSYM.zip
android/fastlane/README.md
ios/fastlane/README.md
Completed
Done! we’ve seen how to automate the deployment of the Flutter app by Fastlane. Fastlane can be used for the Native app, so I hope this blog post helps many developers.
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku
.Deku
created the applications with Flutter.If you have interested, please try to download them for free.