This is a template for the upcoming https://sneakybird.app applications.
I'm crafting this project for personal use with two objectives in mind:
- To be able to kick-start new Flutter projects with all the required integrations and boilerplate ready out of the box
- Learn the production and maintenance process end-to-end
The snippets and the setup script are designed for Ubuntu 24.04. While some of these many may work in other environments, others may require tweaking.
I don't have a Mac, so the iOS building and publishing runs on Codemagic. I have configured Fastlane to run both on Codemagic and locally, but haven't tested the latter.
This template is available UNLICENSED.
The template is ready to use:
- Make sure you have all the prerequisites
- Use this template to create your app GitHub repository
- Run the
setup
script and follow the prompts
allow up to an hour: you'll need to manually add a Play Store App and upload the initial build
The newly initialised project will have Fastlane targets to publish test builds both to App Store Connect and Google Play Console.
See the Roadmap section below for what's to come.
Release to Internal Google Play Console track:
bundle exec fastlane android internal
Release to Apple Test Flight (on Codemagic):
./ci.sh ios-beta
Quick release (no tests, no Shorebird) to Apple Test Flight (on Codemagic):
./ci.sh ios-beta true
Build Android app locally and submit to Test Lab to run integration tests:
pushd android
bundle exec fastlane android test_lab
popd
Set current project for gcloud
tool:
gcloud config set project project-id-placeholder
Backup the source code:
mkdir -p ~/.backups
zip -r ~/.backups/$(basename "$PWD")-$(date +"%Y%m%d%H%M%S").zip ./
Application screen label (the text under the app icon) is set in APP_LABEL_SCREEN
in the project's .env
file.
Just update this value and run:
pushd ios
bundle exec fastlane ios update_app_screen_label
popd
pushd android
bundle exec fastlane android update_app_screen_label
popd
In order to change the app launch icon, create a master png icon, 1024 x 1024 px,
save it as assets/dev/master_app_icon.png
, and run:
. .env
dart run flutter_launcher_icons
cp -r web/icons/Icon-512.png "assets/dev/android/${PRIMARY_APP_LANGUAGE}/images/icon.png"
# Reverts AppIcon back to wider-scoped YES
sed -i 's/ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;/ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;/' ios/Runner.xcodeproj/project.pbxproj
If you're using XCode, you do not need to update the Info.plist
and Runner.entitlements
manually.
The checklist:
- Enable the capability at the Apple Developer Portal
- Update
ios/Runner/Info.plist
and corresponding language files (e.g.ios/Runner/ru.lproj/InfoPlist.strings
for Russian language) (Specification) - Update
ios/Runner/Runner.entitlements
(Specification) - Uncomment the permission in
android/app/src/main/AndroidManifest.xml
- Enable corresponding preprocessor in
ios/Podfile
'sGCC_PREPROCESSOR_DEFINITIONS
Local reference files:
External links:
These are configured once (not per project).
Note: This instruction uses a convention of storing the secrets in .secrets
directory in user's $HOME
.
The files layout is as follows:
~/.secrets/{service-or-product}/{secret-file}
for generic secrets (such as access tokens and passwords)~/.secrets/app/{app_name}/{secret-file}
for application secrets (such as push certificates)
All env variables from this document (except PATH
and related) are defined in ~/.secrets/.bashrc_creds
.
Call it from your .bashrc
. For example source $HOME/.secrets/.bashrc_creds
.
Create the directory, an empty env variables file, the script for backing up secrets, and a password for backups:
mkdir -p ~/.secrets
pushd ~/.secrets
# Create an empty file for the env variables
[ ! -f ".bashrc_creds" ] && touch ".bashrc_creds"
# Create password for secrets backups
[ ! -f "backup-pass" ] && openssl rand -base64 8 > "backup-pass"
# Create a backup script
echo 'pushd ~/.secrets; zip -r -P "$(<backup-pass)" ~/.backups/secrets-$(date +"%Y%m%d%H%M%S").zip ./; popd' > backup.sh
chmod +x backup.sh
popd
Note the password from backup-pass
somewhere safe and use backup.sh
to back up
your ~/.secrets
directory content to ~/.backups
directory as a password-protected zip file.
It is recommended to create a backup every time you add or update a secret. Rehearse the restoration at least once.
Do not change proposed variable names. Some tools, such as Fastlane and Sentry CLI, implicitly rely on some of them.
Install Flutter and Android Studio, if you haven't done it yet.
Make sure you have installed the following basic toolset:
sudo apt-get install curl sed jq yq git ruby python3 python-is-python3 pipx uuidgen openjdk-17-jdk
Make sure you have correctly set JAVA_HOME
(the path may differ, make sure you have correct one):
# ~/.bash_profile
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
Fastlane is a command-line tool written in Ruby that automates common tasks in iOS and Android development workflows, such as building, testing, code signing, and deploying apps to the App Store and Play Store.
Make sure your Ruby gems live in your local user space to avoid unexpected file permission issues:
# ~/.bash_profile
export GEM_HOME="$HOME/.gems"
export PATH="$HOME/.gems/bin:$PATH"
Then install Fastlane
gem install bundler fastlane
Install GitHub CLI.
Login and add permissions:
gh auth login
gh auth refresh -h github.com -s delete_repo -s admin:public_key
Create your personal and CI/CD authentication keys:
# Personal
ssh-keygen -t ed25519 -P "" -C "$(gh api user --jq '.login')"
gh ssh-key add $HOME/.ssh/id_ed25519.pub --title 'Personal'
# CI/CD
mkdir -p $HOME/.secrets/github
ssh-keygen -t ed25519 -P "" -f $HOME/.secrets/github/cicd_id_ed25519 -C "$(gh api user --jq '.login')"
gh ssh-key add $HOME/.secrets/github/cicd_id_ed25519.pub --title 'CICD'
You can check your existing keys here.
Create a repository to store code signing keys for App Store applications and a password to protect the keys:
gh repo create fastlane_match_secrets --private
mkdir -p "$HOME/.secrets/fastlane"
openssl rand -base64 8 >> "$HOME/.secrets/fastlane/match_secrets_password"
Note: backup the password somewhere safe.
Store the SSH auth key path, repo SSH URL, and the code signing keys password in the env variables:
# ~/.secrets/.bashrc_creds
export MATCH_GIT_URL="[email protected]:{YOUR_NAMESPACE}/fastlane_match_secrets.git"
export CICD_GITHUB_SSH_KEY_PATH="$HOME/.secrets/github/cicd_id_ed25519"
export MATCH_PASSWORD_PATH="$HOME/.secrets/fastlane/match_secrets_password"
Slack is used to send build status notifications from Codemagic.
Create an account and install Slack application:
sudo snap install slack
Create #cicd-all
public channel.
Assuming Apple Developer Program membership is active.
Get your "Team ID" from Apple Developer Portal.
Get your App Store Connect (former iTunes Connect - itc) team ID from App Store Connect.
Create and download App Store Connect API (team) key for CI/CD integration. Use App Manager role (or Admin if feeling lucky). Take note of the Issuer ID and the Key ID.
Store the key as $HOME/.secrets/apple/AuthKey_{YOUR_KEY_ID}.p8
Some fastlane actions still require username/password authentication.
Save your iTunes password to $HOME/.secrets/apple/itunes-pass
(in one line).
Set env variables:
# ~/.secrets/.bashrc_creds
export APPLE_DEV_TEAM_ID=...
export APP_STORE_CONNECT_TEAM_ID=...
export APP_STORE_CONNECT_ISSUER_ID=...
export APP_STORE_CONNECT_KEY_IDENTIFIER=...
export APP_STORE_CONNECT_PRIVATE_KEY_PATH="$HOME/.secrets/apple/AuthKey_${APP_STORE_CONNECT_KEY_IDENTIFIER}.p8"
# Your iTunes Id
export ITUNES_ID=...
export ITUNES_PASSWORD_PATH="$HOME/.secrets/apple/itunes-pass"
# This should be your full name if you are an individual developer
export APP_STORE_COMPANY_NAME=...
Create a Google Cloud billing account if you don't have a suitable one.
Install gcloud
CLI.
Use this instruction to create a service account and integrate it with Play Console (do nothing more from that instruction).
You can use your general administration Google Cloud project for the service account (not specific to the app).
Save your JSON access key to $HOME/.secrets/google/{YOUR_JSON_FILE_NAME}
.
Generate code signing key pair for apps uploads to Play Console:
mkdir -p "$HOME/.secrets/google"
openssl rand -base64 8 >> "$HOME/.secrets/google/play-upload-keystore-pass"
keytool -genkeypair \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias upload \
-keystore "$HOME/.secrets/google/play-upload-keystore.jks" \
-storepass $(cat "$HOME/.secrets/google/play-upload-keystore-pass") \
-keypass $(cat "$HOME/.secrets/google/play-upload-keystore-pass")
Add env variables:
# ~/.secrets/.bashrc_creds
export GCLOUD_BILLING_ACCOUNT_ID=...
export SUPPLY_JSON_KEY="$HOME/.secrets/google/{YOUR_JSON_FILE_NAME}"
export PLAY_CONSOLE_UPLOAD_KEYSTORE="$HOME/.secrets/google/play-upload-keystore.jks"
export PLAY_CONSOLE_UPLOAD_KEYSTORE_PASS="$HOME/.secrets/google/play-upload-keystore-pass"
Register with OneSignal.
Do not create an app - the template setup script will do this for you.
Open you organisation in the organisation dashboard,
and copy the org ID from the URL (must be something like a123456f-42cd-3e14-a360-123ab456cdef
).
Go to "Keys & IDs", create an API Key and store it in $HOME/.secrets/onesignal/api-token
file.
Add env variables:
# ~/.secrets/.bashrc_creds
export ONESIGNAL_ORG_ID=...
export ONESIGNAL_API_KEY_PATH="$HOME/.secrets/onesignal/api-token"
Go to your account setting and enable Slack integration.
Save Codemagic API token to $HOME/.secrets/codemagic/auth-token
file and add env variable:
# ~/.secrets/.bashrc_creds
export CM_API_TOKEN_PATH="$HOME/.secrets/codemagic/auth-token"
Install Codemagic CLI suite:
pipx install codemagic-cli-tools
pipx ensurepath
Register with Sentry.
Create two auth tokens in User Auth Tokens,
and save them in $HOME/.secrets/sentry
directory:
api-token-dev
file; roles: Projects: Admin; Organisation: Read (used locally for initial project setup)api-token-ci
file; roles: Release: Admin; Organisation: Read (used locally and on CI server)
Add variables:
# ~/.secrets/.bashrc_creds
export SENTRY_PROJECTS_ADMIN_TOKEN_PATH="$HOME/.secrets/sentry/api-token-dev"
export SENTRY_CI_TOKEN_PATH="$HOME/.secrets/sentry/api-token-ci"
export SENTRY_ORG="{organization-slug}"
export SENTRY_TEAM="{default-team-slug}"
Install GitHub integration.
Install Sentry command line tool:
curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="2.40.0" INSTALL_DIR="$HOME/tools/sentry" sh
Add $HOME/tools/sentry
to your $PATH
.
Shorebird is a service that allows patching production apps without going through the app store review process.
Create a dev account with Shorebird.
Create CI auth token:
shorebird login:ci
Save the token to your $HOME/.secrets/shorebird/auth-token
and add an env variable:
# ~/.secrets/.bashrc_creds
export SHOREBIRD_TOKEN_PATH="$HOME/.secrets/shorebird/auth-token"
Export your contact details for submission to App and Play store. Also export the timezone for your build number timestamps.
# ~/.secrets/.bashrc_creds
export DEV_FIRST_NAME=...
export DEV_LAST_NAME=...
export DEV_PHONE=...
export DEV_EMAIL=...
export DEV_WEBSITE=...
export DEV_FULL_NAME=...
export DEV_ADDRESS_LINE_1=...
export DEV_ADDRESS_LINE_2=...
export DEV_CITY=...
export DEV_STATE=...
export DEV_COUNTRY=...
export DEV_ZIP=...
# Use the timezone string which is compatible with the `date` command
export TZ="Pacific/Auckland"
That's it. Now you're ready to use the template to set up a project.
-
Add integrations:
- OneSignal (push notifications): set up and generate/distribute certificates
- Firebase Remote config
-
Add metadata files for Android (
fastlane supply init
currently fails) -
Add flavours setup (only after I do a real project that uses them)
- Check if badge plugin is useful
-
Screenshots generation framework:
- Move from discontinued
golden_toolkit
- Implement proper fonts loading (maybe it'll fix itself in Alchemist)
- Implement device frames in screenshot generator
- Move from discontinued
-
Self-checkin service for beta testers, for both Android and iOS, like Boarding, but more stable
-
See how an automatic translation service can be added (or built via an AI API)