As a developer at XETICS I am also responsible for our CI/CD stack which is done using Gitlab-CI. As I haven’t found any guide about using Gitlab-CI for creating a CI/CD stack which also includes building and deploying an iOS application I will simply publish our approach.
I am assuming that you already have a certain knowledge about Gitlab-CI. If not check https://docs.gitlab.com/ee/ci/
In the end, our pipeline looks like this:

You can find the complete code in my Github repository:
https://github.com/fezu54/flutter-gitlab-ci-example
Prerequisites
To be able to build an iOS App you definitely need a Mac! We’re using the new Mac mini which should be powerful enough
Before we started automating this process, our App was already present in both app stores. Hence, the approach might differ if your app is not published until now.
Android Build
Let’s start with a simple CI/CD which runs all tests with coverage and builds the Android APK. As we are are using Gitlab Runner in a Docker container for the Android build, we need to create the used docker image first.
Checkout https://docs.gitlab.com/runner/install/docker.html for more information about running Gitlab Runner in a Docker container.
To increase our build time, we’ll create an image which already contains all needed stuff like Android SDK, Flutter SDK and the Java run time. Feel free to adjust it according to your needs. The image also already contains Fastlane. I’ll come to that in a later point 😉.
The image will do the following:
- Set Android and Flutter SDK version
- Install needed dependencies
- Download Android SDK and apply licenses
- Download Flutter SDK
- Install Fastlane
- Remind you to not forget to update the Flutter version on the Mac Mini 🙂
FROM fedora:latest
ENV ANDROID_COMPILE_SDK=29
ENV ANDROID_BUILD_TOOLS=29.0.0
ENV ANDROID_SDK_TOOLS=3859397
ENV FLUTTER_CHANNEL=stable
ENV FLUTTER_VERSION=1.5.4-hotfix.2-${FLUTTER_CHANNEL}
# install some needed dependencies
# -y will auto confirm the command
# to keep the container as small as possible, clean all will delete
# the downloaded dependencies
RUN dnf update -y
&& dnf install -y wget tar unzip ruby ruby-devel make autoconf automake redhat-rpm-config lcov
gcc gcc-c++ libstdc++.i686 java-1.8.0-openjdk-devel xz git mesa-libGL mesa-libGLU rubygems
&& dnf clean all
ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
ENV PATH=$PATH:$JAVA_HOME
# Download Android SDK
# echo "y" will auto confirm license agreement
RUN wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip
&& unzip android-sdk.zip -d /opt/android-sdk-linux/
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "platform-tools"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "extras;android;m2repository"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "extras;google;google_play_services"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "extras;google;m2repository"
&& yes | /opt/android-sdk-linux/tools/bin/sdkmanager --licenses || echo "Failed"
&& rm android-sdk.zip
# make SDK tools available for CI
ENV ANDROID_HOME=/opt/android-sdk-linux
ENV PATH=$PATH:/opt/android-sdk-linux/platform-tools/
# Download Flutter SDK
RUN wget --quiet --output-document=flutter.tar.xz https://storage.googleapis.com/flutter_infra/releases/${FLUTTER_CHANNEL}/linux/flutter_linux_v${FLUTTER_VERSION}.tar.xz
&& tar xf flutter.tar.xz -C /opt
&& rm flutter.tar.xz
# make Flutter available for CI
ENV PATH=$PATH:/opt/flutter/bin
# if you want to autoincrease Flutter version number the next three lines are helpful
ENV PATH=$PATH:/opt/flutter/bin/cache/dart-sdk/bin
RUN flutter pub global activate pubspec_version
ENV PATH="$PATH":"/opt/flutter/.pub-cache/bin"
RUN echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "emulator"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "system-images;android-18;google_apis;x86"
&& echo "y" | /opt/android-sdk-linux/tools/bin/sdkmanager "system-images;android-${ANDROID_COMPILE_SDK};google_apis_playstore;x86"
# install Fastlane
RUN gem install fastlane
RUN dnf update -y
&& dnf install -y pulseaudio-libs mesa-libGL mesa-libGLES mesa-libEGL
&& dnf clean all
RUN echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
&& echo "Don't forget to change the Flutter version also on the Mac mini if you've changed it here!"
&& echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
If the Docker image fits your need, you can build it with
docker build -f Dockerfile xetics/flutter:1.5.4-stable .
I decided to tag the image with the Flutter version which is used within the container. Hence, I’ll tag it with 1.5.4-stable
iOS Build
First, unpack your new Mac 😃 . No seriously, as we need to do some preparation for the Android build, we need to do so for the iOS App. First thing is that we have to install Gitlab Runner as Shell Executor on our new Mac.
I haven’t found anything about how to use an Docker image on the Mac instead. If you know how, let me know!
Sign in with an user having admin rights and download the Runner executable:
sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
Next, make it executable for everyone
sudo chmod +x /usr/local/bin/gitlab-runner
Now, create a new user gitlab-ci
which doesn’t need to be in the sudoers file. This one will be the user which executes the Gitlab Runner later on. As the executor is running in user mode, set the user to be auto logged in on restart.
- Log in as
gitlab-ci
and register the Runner:
gitlab-runner register
- Register your Gitlab instance:
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com ) https://gitlab.com
- Enter the token:
Please enter the gitlab-ci token for this runner
xxx
- Enter some description:
Please enter the gitlab-ci description for this runner
[hostname]my-runner
- Enter the tags which will be associated with this Runner. We’ll tag it as
ios
Please enter the gitlab-ci tags for this runner (comma separated):
ios
- Enter the executor:
Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
shell
- Afterwards, install the service and start it:
cd ~
gitlab-runner install
gitlab-runner start
Now, you’ve setup the Gitlab Runner also install:
Code signing
To be honest, this is a mess on both platforms and the most disappointing part. It took me quite some time to get it done. To get quickly over it, let’s do all the preparation in advance. If that’s done, we can just concentrate on the fun part.
Android signing
For Android, I’ve found this awesome stackoverflow post which you can easily follow. Kudos to MatPag and thanhbinh84:
https://github.com/fezu54/flutter-gitlab-ci-example
After you have all your key store setup done, we will store it base64 encoded in Gitlab’s CI/CD secure environment variables:
- Encode the keystore:
base64 key.jks
- Copy the base64 output and create a variable called
PLAY_STORE_UPLOAD_KEY
. I would recommend to put single quotes''
around the base64 string. - Save the store password as
STORE_PASSWORD
variable - Save the key password as
KEY_PASSWORD
variable - Save the key alias as
KEY_ALIAS
variable
To make the deployment via Fastlane working, you need to to get API access to Google Play console. To get an api key json file, follow the steps of the Collect your Google credentials section of the Fastlane guide
Only the account owner can grant API access. So be kind to your manager 😃
- Save the content of the key.json file as
JSON_KEY_DATA
variable.
iOS signing
Code signing for iOS is pretty straight forward if you use fastlane match. The disadvantage is, that you have to revoke your already existing certificates. As they have a well described guide about setting it up, I will just refer to it. Take your time and read through it completely. This is really important!
To store the certificates, I’ve just created another repository in our Gitlab instance. We’re also using a shared Apple Developer Portal account. Hence, I can recommend that you are using one too.
https://github.com/fezu54/flutter-gitlab-ci-example
After you’re done, we have to add some more Gitlab environment variables:
- Add the password of your newly created Apple Developer Portal account as
FASTLANE_PASSWORD
- Add the https git url of your certificates repo as
MATCH_GIT_URL
- Add the passphrase with which you encrypted the certificates as
MATCH_PASSWORD
Summary:
Congratulations! You’re are just done with the most annoying part. Your Gitlab CI/CD environment variable should look like this now:

Fastlane
Fastlane is an open-source tool suite which automates beta deployments and releases for iOS and Android apps. With it, you can easily do all of this tasks directly in your CI pipeline.
https://github.com/fezu54/flutter-gitlab-ci-example
To prepare the Flutter project, log in with your personal account on the Mac (do not use the gitlab-ci user because you have to checkout the repository ).
Before we’re going on, we need to install fastlane for our user. Run gem install fastlane
or brew cask install fastlane
. Please do not run these commands with sudo
.
Android setup
Check out your Flutter project from Gitlab. Afterwards, open a terminal window and navigate to the android
folder of the Flutter project and run fastlane init
.


If you’re stuck at $ bundle update, you probably installed fastlane with sudo. Remove it and reinstall it without admin rights.
Now, your folder structure should look like this:

Appfile
Now, it’s time to do some configuration. First, open the Appfile
and check that it just contains a single line, your package name:
package_name("com.flutter.medium") # e.g. com.krausefx.app
Ensure that the package name matches the package
tag in your AndroidManifest.xml
as well as your applicationId
in the app/build.gradle
file.
Fastfile
Mostly all of the magic is done in the Fastfile
. We will simply follow the tracks already provided by Google Play. Hence, after we build the APK via Flutter, we’re going to upload it once into the internal
Play Store track. From there, the APK will just be promoted through the upper tracks.
Because we don’t have the need for Beta users in our company yet, the Fastfile also contains a lane which promotes the APK from Alpha to Production directly.
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
update_fastlane
default_platform(:android)
platform :android do
desc "Submits the APK to Google Play internal testing track"
lane :upload_to_play_store_internal do
upload_to_play_store(
track: 'internal',
apk: '../build/app/outputs/apk/release/app-release.apk',
json_key_data: ENV['JSON_KEY_DATA']
)
end
desc "Promote Internal to Alpha"
lane :promote_internal_to_alpha do
upload_to_play_store(
track: 'internal',
track_promote_to: 'alpha',
json_key_data: ENV['JSON_KEY_DATA']
)
end
desc "Promote Alpha to Beta"
lane :promote_alpha_to_beta do
upload_to_play_store(
track: 'alpha',
track_promote_to: 'beta',
json_key_data: ENV['JSON_KEY_DATA']
)
end
desc "Promote Alpha to Production"
lane :promote_alpha_to_production do
upload_to_play_store(
track: 'alpha',
track_promote_to: 'production',
json_key_data: ENV['JSON_KEY_DATA']
)
end
desc "Promote Beta to Production"
lane :promote_beta_to_production do
upload_to_play_store(
track: 'beta',
track_promote_to: 'production',
json_key_data: ENV['JSON_KEY_DATA']
)
end
end
Gemfile
The Gemfile will just contain Fastlane
as our RubyGem:
source "https://rubygems.org"
gem "fastlane"
Add all, the Appfile
, Fastfile
and Gemfile
to your VCS.
Now, let’s have a look at the iOS setup.
iOS setup
The iOS setup is done the same way we’ve already done it for Android. Hence, navigate to the ios
folder inside your Flutter root and run again fastlane init
. Simply choose manual setup
because we’re going to setup all stuff directly. You have to press two times Enter
and you’re done. Of course, I would recommend to read the stuff Fastlane is trying to tell you 😉
If you want to try out one of the other offers of Fastlane, be sure you execute this on the Mac.

The folder structure should look similar to the Android ones:

Appfile
Let’s start again with the Appfile
It looks a bit different than its Android counterpart:
app_identifier("flutter.com.medium") # The bundle identifier of your app
apple_id("marty-mcfly@icloud.com") # Your Apple email address
itc_team_id("20151021") # App Store Connect Team ID
team_id("TEAM1985") # Developer Portal Team ID
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
Fastfile
For iOS we will follow the flow Apple is providing in its App Store Connect. Hence, after we‘ve build the IPA via Flutter, we‘ll upload it to Apple‘s TestFlight.
Afterwards, we will simply promote the binary to the App Store. Because of the restrictions of Apple, we can‘t upload the binary as easy as we can for Google Play. Technically, the IPA will be submitted for review. After we passed the review, our update is published in the App Store.
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
update_fastlane
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
# This var is needed to work around proxy problems on our Gitlab instance. See https://github.com/fastlane/fastlane/issues/13572
ENV["DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS"] = "-t Signiant"
# build the binary
gym(
scheme: 'Release',
export_method: 'app-store',
export_options: { compileBitcode: false},
output_directory: './build',
output_name: 'app-beta'
)
# submit to TestFlight
pilot(
ipa: './build/app-beta.ipa',
app_identifier: ENV['APP_IDENTIFIER'],
skip_submission: false,
changelog: ENV['CI_COMMIT_MESSAGE'],
apple_id: ENV['APPLE_ID']
)
end
desc "Promote the beta build to App Store"
lane :submit_review do
deliver(
build_number: ENV['CI_PIPELINE_ID'],
app_identifier: ENV['APP_IDENTIFIER'],
app_version: ENV['FLUTTER_APP_VERSION'],
submit_for_review: true,
automatic_release: true,
force: true, # This skips the HTML report verification
skip_metadata: true,
skip_screenshots: true,
skip_binary_upload: true
)
end
desc "Post updates on Twitter"
lane :notify_twitter_followers do
twitter(
access_token: ENV['TWITTER_ACCESS_TOKEN'],
access_token_secret: ENV['TWITTER_ACCESS_TOKEN_SECRET'],
consumer_key: ENV['TWITTER_CONSUMER_API_KEY'],
consumer_secret: ENV['TWITTER_CONSUMER_API_SECRET_KEY'],
message: "A new version of our Value Stream Tracking App is available in the App Store: https://apps.apple.com/de/app/xetics-wertstromanalyse/id1463187782"
)
end
endGemfile
The Gemfile is exactly the same like it is for Android:
source "https://rubygems.org"
gem "fastlane"
gem "twitter"
Gitlab
Test stage
Our first stage will be pretty simple. It will run all tests with coverage and upload the generated LCOV report to Gitlab. From there, you might want to publish it to Gitlab Pages. I haven’t done it until now. Sorry for that. If you already have an approach for that, feel free to post it into the comments 😃.
stages:
- test
variables:
LC_ALL: "en_US.UTF-8"
LANG: "en_US.UTF-8"
.android_docker_image:
image: xetics/flutter:1.5.4-stable
tags:
- flutter-android
test:
extends: .android_docker_image
stage: test
script:
- flutter test --coverage
- genhtml coverage/lcov.info --output=coverage
artifacts:
paths:
- coverage/
expire_in: 5 days
The testing stage will use our previously created docker image to run all unit tests.
Package stage
From now on, our stages will run parallel. The iOS related one on the Mac, the Android one on our Docker image.
stages:
- test
- package
...
.android_key_store:
extends: .android_docker_image
before_script:
# this will fetch the base64 encoded key store from our CI variables, decode it and place it underneath the path specified in build.gradle
- echo "$PLAY_STORE_UPLOAD_KEY" | base64 --decode > android/key.jks
only:
- master
build_android:
stage: package
extends: .android_key_store
script: flutter build apk --release --build-number=$CI_PIPELINE_ID
artifacts:
paths:
- build/app/outputs/apk/release/app-release.apk
expire_in: 1 day
build_ios:
stage: package
script:
# although fastlane also is capable of building the ipa, we are doing it with flutter to ensure it has the same versioning like the Android app
- flutter build ios --release --build-number=$CI_PIPELINE_ID
artifacts:
untracked: true
expire_in: 1 day
tags:
- ios
only:
- master
We’re using flutter build
for creating both binaries to ensure that they’ll use the same versioning.
For Android, we need to grab the already prepared key store to sign the APK for release.
For iOS, the signing stuff will at first play a role in the next stage. But here, I’m simply uploading everything which is not under VCS control to Gitlab to reuse it in the next stage. This is more a dirty workaround as I wasn’t able to figure out what stuff I exactly need and what not. If you know, please share your knowledge 😃
Test deployment
As the binaries are build now, it’s time to upload them to the stores. This will be done in our test_deployment
stage
stages:
- test
- package
- test_deployment
...
.setup_fastlane_android:
extends: .android_key_store
before_script:
- cd android/
# because the Docker container runs as root currently, we won't do any user-install. Otherwise, it will fail with
# $ gem install --user-install bundler
# ERROR: Use --install-dir or --user-install but not both
- gem install bundler
- bundle install
.setup_fastlane_ios:
before_script:
- cd ios/
- gem install --user-install bundler
- bundle install --path vendor/bundle
- bundle exec fastlane match
- export FLUTTER_APP_VERSION=$(pubver -d ../. get)
tags:
- ios
only:
- master
...
ios_testflight_beta_deployment:
stage: test_deployment
extends: .setup_fastlane_ios
dependencies:
- build_ios
script: bundle exec fastlane beta
android_play_store_internal_and_alpha_deployment:
stage: test_deployment
extends: .setup_fastlane_android
dependencies:
- build_android
script:
- bundle exec fastlane upload_to_play_store_internal
- bundle exec fastlane promote_internal_to_alpha
For iOS we need to sign the code now. Because we already setup Fastlane match before, running bundle exec fastlane match
is enough. In addition, we need to set displayed app version string (like 1.0.0). Therefore, we will simply use pubver
command from pubspec_version
you should have installed before. The environment variable will be read by Fastlane afterwards.
Productive deployment
The last missing stage is of course making your App available to the outside.
stages:
- test
- package
- test_deployment
- productive_deployment
...
app_store_submit_to_review:
stage: productive_deployment
extends: .setup_fastlane_ios
dependencies:
- ios_testflight_beta_deployment
script: bundle exec fastlane submit_review
when: manual
allow_failure: false
android_play_store_productive_deployment:
stage: productive_deployment
extends: .setup_fastlane_android
dependencies:
- android_play_store_internal_and_alpha_deployment
script: bundle exec fastlane promote_alpha_to_production
when: manual
allow_failure: false
Because we don’t want to auto push the update to the stores, the stages are marked as manual
. Hence, after our QA give the final go, someone can simply start the release via a single click in Gitlab.
Additional stuff
You may have already seen in the picture in the beginning, that we have an additional pipeline after the release was published. There, we’re informing our users via Twitter that a new release is available. To achieve that the following adjustments have to be done:
- Add
gem "twitter"
toios/Gemfile
andandroid/Gemfile
- Add new lane to both
ios/fastlane/Fastfile
andandroid/fastlane/Fastfile
:
desc "Post updates on Twitter"
lane :notify_twitter_followers do
twitter(
access_token: ENV['TWITTER_ACCESS_TOKEN'],
access_token_secret: ENV['TWITTER_ACCESS_TOKEN_SECRET'],
consumer_key: ENV['TWITTER_CONSUMER_API_KEY'],
consumer_secret: ENV['TWITTER_CONSUMER_API_SECRET_KEY'],
message: "<Your tweet>"
)
end
- Add new stage in
.gitlab-ci.yml
:
tweet_about_android_updates:
stage: notification
extends: .setup_fastlane_android
dependencies:
- android_play_store_productive_deployment
script: bundle exec fastlane notify_twitter_followers
when: on_success
tweet_about_ios_updates:
stage: notification
extends: .setup_fastlane_ios
dependencies:
- app_store_submit_to_review
script: bundle exec fastlane notify_twitter_followers
when: on_success
Change logs
To fill up the stores What’s new section, we are also providing change logs for each possible release. The way how you have to provide the change logs differs from Android and iOS. Because we don’t want to maintain those things twice, we’ve created our own approach.
- Create
changelogs
directly inside the root of your project - Create a folder for each language code you’re supporting
- Create localized
release_notes.txt
for each language code

For iOS we can simply take these change logs. Therefore, adjust the submit_review
lane in ios/fastlane/Fastfile
to also fetch the release notes:
...
desc "Promote the beta build to App Store"
lane :submit_review do
deliver(
build_number: ENV['CI_PIPELINE_ID'],
app_identifier: ENV['APP_IDENTIFIER'],
app_version: ENV['FLUTTER_APP_VERSION'],
release_notes: {
'default' => File.read('./../../changelogs/en-US/release_notes.txt'),
'en-US' => File.read('./../../changelogs/en-US/release_notes.txt'),
'de-DE' => File.read('./../../changelogs/de-DE/release_notes.txt')
},
submit_for_review: true,
automatic_release: true,
force: true, # This skips the HTML report verification
skip_metadata: false,
skip_screenshots: true,
skip_binary_upload: true
)
end
...
Android is a bit more complex as it expects the release notes file to be named with the version code the app is going to be released. To achieve that a bit of ruby scripting is needed:
- Add
gem "fileutils"
toandroid/Gemfile
- Add dependencies to fileutils and create a language code array in
android/fastlane/Fastfile
...
default_platform(:android)
platform :android do
require ‘fileutils’
languages = ["de-DE", "en-US"]
...
- Add two helper function to your Fastfile:
...
def prepareProductionChangelogs(languageCode)
puts "--- Preparing productive changelogs for language code '#{languageCode}' ---"
sharedChangelogs = "./../../changelogs/#{languageCode}"
specificChangelogs = getSpecificChangelogsDirectory(languageCode)
createChangelogsFolder(specificChangelogs)
FileUtils.cp("#{sharedChangelogs}/release_notes.txt", "#{specificChangelogs}/#{ENV['CI_PIPELINE_ID']}.txt")
end
def createChangelogsFolder(changelogsDirectory)
FileUtils.mkdir_p changelogsDirectory
end
def getSpecificChangelogsDirectory(languageCode)
"./metadata/android/#{languageCode}/changelogs"
end
...
- Adjust the lane where you are going public (beta or production)
...
desc "Promote Alpha to Production"
lane :promote_alpha_to_production do
languages.each { |languageCode| prepareProductionChangelogs(languageCode)}
upload_to_play_store(
track: 'alpha',
track_promote_to: 'production',
json_key_data: ENV['JSON_KEY_DATA']
)
end
...
Internal change logs
To make the life of our QA easier, the change logs for the internal releases differs. There, we are using the last commit message which always points to the merge request of the changes.

For iOS add changelog:
command to pilot
in ios/fastlane/Fastfile
:
...
pilot(
ipa: './build/app-beta.ipa',
app_identifier: ENV['APP_IDENTIFIER'],
skip_submission: false,
changelog: ENV['CI_COMMIT_MESSAGE'],
apple_id: ENV['APPLE_ID']
)
...
Again, for Android a bit more work has to be done in the android/fastlane/Fastfile
:
- add another helper function:
...
def prepareTestingChangelogs(languageCode)
puts "--- Preparing test changelogs for language code '#{languageCode}' ---"
specificChangelogs = getSpecificChangelogsDirectory(languageCode)
createChangelogsFolder(specificChangelogs)
File.open("#{specificChangelogs}/#{ENV['CI_PIPELINE_ID']}.txt", "w") {|file| file.write(ENV['CI_COMMIT_MESSAGE']) }
end
...
- adjust the
upload_to_play_store_intenal
lane:
...
desc "Submits the APK to Google Play alpha testing track"
lane :upload_to_play_store_internal do
languages.each { |languageCode| prepareTestingChangelogs(languageCode)}
upload_to_play_store(
track: 'internal',
apk: '../build/app/outputs/apk/release/app-release.apk',
json_key_data: ENV['JSON_KEY_DATA']
)
end
...
Screenshots
Another thing you can fully automate is taking of localized screenshots to promote your App in the stores. Since I haven’t set this up until now, I will just refer to the great post of Maurice McCabe who already documented it perfectly:
https://github.com/fezu54/flutter-gitlab-ci-example