mobile: build Google Play bundles and publish on demand - #430
Merged
Merged
Conversation
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.
Contributor
There was a problem hiding this comment.
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.
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'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 fromandroid/key.properties(Flutter's standard layout) orANDROID_KEYSTORE_PATH/ANDROID_KEYSTORE_PASSWORD/ANDROID_KEY_ALIAS/ANDROID_KEY_PASSWORD. With neither present it falls back to the debug key with a warning, soflutter run --releaseand local APKs are unaffected.make mobile-app-bundlerunsflutter build appbundle --releaseand fails fast, pointing at the docs, when no upload key is configured.MOBILE_BUILD_NAME/MOBILE_BUILD_NUMBERoverride the pubspec version so Play always sees a freshversionCode.google-services.jsonandlibsam.socopy steps are factored intomobile-app-google-servicesandmobile-app-jnilibs-arm64, shared by all three app targets.CI
.github/workflows/mobile.yml):v*tags, runs in parallel with goreleaser; theattach-to-releasejob waits for goreleaser to create the release, then uploads the APK and.aab;workflow_dispatchfrom any ref, with atrackinput (none= build only;internal/alpha/beta/productionpublish) and an optionalbuild_numberoverride. APK and.aabare always available as run artifacts.publishmints a short-livedandroidpublisheraccess token through Workload Identity Federation (repo varsWIF_PROVIDER_NAME_APP_STORE/SERVICE_ACCOUNT_EMAIL_APP_STORE) and runshack/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.ymlis pure goreleaser again; the Java/NDK/Flutter setup and the "restore files rewritten by Flutter" workaround go with the mobile steps.Housekeeping
.gitignorenow ignores*.jks,*.keystore,key.propertiesandgoogle-services.jsonrepo-wide (the tracked.tmplis unaffected).mobile/sam-node-app/README.mdgains 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 -nfor all mobile targets;bash -nand argument-validation runs ofhack/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.ANDROID_KEYSTORE_*,ANDROID_KEY_*,GOOGLE_SERVICES_JSON_BASE64) and the*_APP_STOREvariables are already set ongoogle/sam.