Uploading/promoting builds is done via Fastlane lanes — see FASTLANE.md for what each lane does and how they’re triggered, if you want that detail alongside this setup guide.
This project automates as much of publishing as Google allows any tool to
automate: building the signed .aab, uploading it, pushing the store
listing text (title/description/changelog), the icon and feature graphic,
and even the screenshots (captured by a script, not by hand). Once the
one-time setup below is done, shipping is one command and touches no
Play Console form.
Google deliberately keeps a few things human-only, as legal/security controls. No tool, including Google’s own CLI, can script around these:
| Task | Automatable? |
|---|---|
Build + sign the .aab |
✅ Yes — CI does this |
| Upload the binary to a track | ✅ Yes — Fastlane upload_to_play_store |
| Store listing text (title, description, changelog) | ✅ Yes — fastlane/metadata/android/en-US/*.txt, already written |
| Icon + feature graphic | ✅ Yes — placeholder generated by scripts/generate_placeholder_graphics.ps1, swap for real art anytime |
| Screenshots | ✅ Yes — scripts/capture_screenshots.sh drives an emulator via adb, no manual screenshotting |
| Creating a brand-new app listing | ❌ No — Play Console UI only, one time |
| Content rating questionnaire | ❌ No — Play Console UI only, one time (legal attestation) |
| Data safety form | ❌ No — Play Console UI only, one time (legal attestation) |
| Granting the service account API access | ❌ No — a deliberate security control, one time |
Everything in the ❌ column is a one-time cost. After it’s done once, every future release is fully automated.
For a click-by-click walkthrough of the Play Console screens involved (testers, countries/regions, content rating questionnaire, creating a production release, submitting for review), see play-console-manual-steps.md.
✅ Already done — GitHub Pages is enabled (branch main, folder
/docs), live at:
https://itmc-cloud.github.io/itmc-business-os/privacy-policy.html
(It can take a couple of minutes after enabling for the first build to
finish — if the link 404s immediately after setup, wait and retry.) From
here on, editing docs/privacy-policy.md and pushing to main updates the
live page automatically — no further manual step.
Still needed: edit the placeholder contact email in
docs/privacy-policy.md before using this URL in the Play Console listing.
fastlane/metadata/android/en-US/ (copy-paste — this is the one time you
touch these fields manually, since the app doesn’t exist yet for the API
to update): short/full description, icon, feature graphic, and the
screenshots from scripts/capture_screenshots.sh (run it first — see
docs/RUNNING.md). Also: the privacy policy URL from step 1, content
rating questionnaire, target audience, data safety form, category,
contact details — for the content rating and data safety forms
specifically, see
docs/store-listing-answers.md for the exact
answer to every question, based on what this app actually does.flutter build appbundle --release
(Internal testing track, via “Create release” in the App bundle explorer.) This works with debug signing until step 3 below is done — fine for this first internal upload; use real signing for anything you intend to keep live.
keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
Keep upload-keystore.jks outside the repo — it’s git-ignored on
purpose. Losing it means you can never update the app again under this
listing.
Local builds: copy android/key.properties.example to
android/key.properties and fill in the real values. CI: see step 5 below —
the keystore goes in as a base64 secret, not a repo file.
This is the credential the automated pipeline uses to authenticate as you — do these four parts in order, log in to both consoles with the same Google account you use for Play Console.
itmc-estimator-publishing → Create. Wait ~10–20s, then
make sure it’s selected in that same dropdown.play-publisher → Create and
Continue..json downloads automatically.fastlane/play-service-account.json in this project (already
git-ignored — never commit it).Google removed the old “link a Cloud project” step — there’s no separate API access/linking page anymore. Service accounts are now invited exactly like a human teammate, via Users & Permissions:
play-publisher@<your-project-id>.iam.gserviceaccount.com
— not a real email address, that’s expected).Repo → Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
ANDROID_KEYSTORE_BASE64 |
base64 -w0 upload-keystore.jks (Windows: [Convert]::ToBase64String([IO.File]::ReadAllBytes("upload-keystore.jks"))) |
ANDROID_KEYSTORE_PASSWORD |
the storePassword from step 3 |
ANDROID_KEY_ALIAS |
upload (or your alias) |
ANDROID_KEY_PASSWORD |
the keyPassword from step 3 |
PLAY_SERVICE_ACCOUNT_JSON |
full contents of the service account JSON from step 4 |
# 1. (optional) refresh screenshots if the UI changed meaningfully
scripts/capture_screenshots.sh
git add fastlane/metadata
git commit -m "Update screenshots"
# 2. tag and push
git tag v1.0.1
git push origin v1.0.1 --tags
That tag push triggers .github/workflows/release.yml, which builds the
signed .aab and uploads it — along with whatever is currently in
fastlane/metadata/ (listing text, icon, feature graphic, screenshots) — to
the internal testing track. Promote to production from Play Console, or
trigger the workflow manually (Actions tab → “Release to Google Play” →
“Run workflow”) and pick production directly, which runs
fastlane android deploy_production — binary + full listing, no manual
Play Console steps at all.
flutter run
The Dockerfile is only for reproducible release builds; not needed for day-to-day development.