Skip to content

mobile: build Google Play bundles and publish on demand - #430

Merged
aojea merged 2 commits into
google:mainfrom
aojea:mobile-play-bundle
Sep 18, 2026
Merged

aojea merged 2 commits into
google:mainfrom
aojea:mobile-play-bundle

Conversation

@aojea

@aojea aojea commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Release builds of SAM Connect were signed with the debug key, which Google Play rejects, and the only CI path was an APK baked into the goreleaser workflow. This wires up a Play-ready bundle, splits the mobile build into its own workflow, and adds on-demand publishing.

Signing and bundle

  • android/app/build.gradle.kts: release builds sign with an upload key from android/key.properties (Flutter's standard layout) or ANDROID_KEYSTORE_PATH / ANDROID_KEYSTORE_PASSWORD / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD. With neither present it falls back to the debug key with a warning, so flutter run --release and local APKs are unaffected.
  • make mobile-app-bundle runs flutter build appbundle --release and fails fast, pointing at the docs, when no upload key is configured. MOBILE_BUILD_NAME / MOBILE_BUILD_NUMBER override the pubspec version so Play always sees a fresh versionCode.
  • The duplicated google-services.json and libsam.so copy steps are factored into mobile-app-google-services and mobile-app-jnilibs-arm64, shared by all three app targets.

CI

  • New Mobile App workflow (.github/workflows/mobile.yml):
    • on v* tags, runs in parallel with goreleaser; the attach-to-release job waits for goreleaser to create the release, then uploads the APK and .aab;
    • on workflow_dispatch from any ref, with a track input (none = build only; internal / alpha / beta / production publish) and an optional build_number override. APK and .aab are always available as run artifacts.
    • publish mints a short-lived androidpublisher access token through Workload Identity Federation (repo vars WIF_PROVIDER_NAME_APP_STORE / SERVICE_ACCOUNT_EMAIL_APP_STORE) and runs hack/publish-play.sh, which drives the Play Developer Publishing API (edit → upload → track → commit) with curl + jq. The token reaches curl via a 0600 header file, never argv. No long-lived service-account key is stored.
  • release.yml is pure goreleaser again; the Java/NDK/Flutter setup and the "restore files rewritten by Flutter" workaround go with the mobile steps.

Housekeeping

  • Root .gitignore now ignores *.jks, *.keystore, key.properties and google-services.json repo-wide (the tracked .tmpl is unaffected).
  • mobile/sam-node-app/README.md gains a "Publishing to Google Play" section: creating the upload key, key.properties, the make target, the workflow triggers and the Play Console / WIF prerequisites.

Verification

  • make -n for all mobile targets; bash -n and argument-validation runs of hack/publish-play.sh; YAML parses. No Flutter/JDK/NDK on the dev box, so the first real build is the CI run of this branch.
  • Repository secrets (ANDROID_KEYSTORE_*, ANDROID_KEY_*, GOOGLE_SERVICES_JSON_BASE64) and the *_APP_STORE variables are already set on google/sam.

Release builds were signed with the debug key, which Google Play rejects,
and the only CI path was an APK baked into the goreleaser workflow.

Gradle now signs release builds with an upload key read from
android/key.properties or ANDROID_KEYSTORE_* environment variables,
falling back to the debug key so `flutter run --release` keeps working.
`make mobile-app-bundle` produces the .aab and refuses to run without a
key; MOBILE_BUILD_NAME/MOBILE_BUILD_NUMBER override the pubspec version
so Play always sees a fresh versionCode.

The mobile build moves out of release.yml into its own Mobile App
workflow that runs on release tags in parallel with goreleaser (waiting
for the release to exist before attaching the APK and bundle) and on
workflow_dispatch from any ref. A dispatch may name a Play track; the
publish job mints a short-lived androidpublisher token via Workload
Identity Federation and drives the Play Developer API with
hack/publish-play.sh, passing the token through a header file rather
than argv. No long-lived service-account key is stored.

Keystores and google-services.json are git-ignored repo-wide: they are
generated next to the checkout and must never be committed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for building and publishing Android App Bundles (AAB) to Google Play. Key changes include a new mobile-app-bundle Makefile target, a publish-play.sh script for automated releases, and updated Gradle configuration to support secure release signing via environment variables or key.properties. The review feedback provides actionable improvements for robust error handling in the build and publishing scripts, as well as safer Gradle configuration practices.

Comment thread Makefile Outdated
Comment thread mobile/sam-node-app/android/app/build.gradle.kts Outdated
Comment thread hack/publish-play.sh
Comment thread hack/publish-play.sh
Review follow-ups: mobile-app-bundle fails fast when any of the four
ANDROID_* signing variables is missing rather than only the keystore
path (Gradle would otherwise fall back to the debug key and Play would
reject the bundle); relative storeFile paths in key.properties resolve
against android/, where that file lives; and publish-play.sh checks the
edit id and versionCode instead of continuing with 'null'.
@aojea
aojea merged commit e6861a0 into google:main Sep 18, 2026
18 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant