itmc-business-os

Publishing to Google Play

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.

What is — and isn’t — automatable

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.

One-time setup

1. Host the privacy policy (needed for the store listing)

✅ 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.

2. Create the app in Play Console (one-time, manual — unavoidable)

  1. Go to Play Console → “Create app”.
  2. Fill in the app name, default language, app/game type, free/paid.
  3. Complete the store listing using the text already prepared in 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.
  4. Upload one build to get the app out of “draft” state:
    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.

3. Generate your app signing key

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.

4. Connect Google Cloud to Play Console (service account + API access)

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.

A. Create a Google Cloud project

  1. Go to console.cloud.google.com.
  2. Top-left project dropdown → “New Project”.
  3. Name it e.g. itmc-estimator-publishing → Create. Wait ~10–20s, then make sure it’s selected in that same dropdown.

B. Enable the Play Developer API

  1. Left sidebar (or top search) → “APIs & Services” → “Library”.
  2. Search “Google Play Android Developer API” → open it → “Enable”.

C. Create the service account + key

  1. Left sidebar → “IAM & Admin” → “Service Accounts”.
  2. “Create Service Account” → name it e.g. play-publisher → Create and Continue.
  3. Skip role assignment on this screen (Continue / Done) — permissions are granted from the Play Console side instead, in part D.
  4. Open the service account → “Keys” tab → “Add Key” → “Create new key” → JSON → Create. A .json downloads automatically.
  5. Save it somewhere safe outside the repo. This file is fastlane/play-service-account.json in this project (already git-ignored — never commit it).

D. Grant it access in Play Console

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:

  1. Play Console → “Users and permissions” in the left sidebar.
  2. Click “Invite new users”.
  3. In the email field, paste the service account’s email from part C (looks like play-publisher@<your-project-id>.iam.gserviceaccount.com — not a real email address, that’s expected).
  4. Assign permissions — you want at least “Release apps to testing tracks” (add production release permission too once you’re ready to automate full production releases). Scope to a specific app once one exists, or leave it at account level for now.
  5. Click “Invite user”.

5. Configure GitHub Actions secrets

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

Every release after that

# 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.

Building locally without any of this

flutter run

The Dockerfile is only for reproducible release builds; not needed for day-to-day development.