Android release build & Drive upload
One script bumps the build number, builds a signed Android release, and uploads it to Google Drive for testers.
Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
TL;DR -
scripts/release-android.shruns the entire Android release loop from a developer machine: it bumps the build number, builds a signed app, checks the file, and uploads it to a shared Google Drive folder for testers.
Who this is for -DevOps & Operations and Mobile: the day-to-day command and flags. QA: how to grab the latest tester build from Drive. Engineering: the one-time signing/rclone setup and the step-by-step of what the script does.
Getting an Android build into testers' hands has several fiddly steps —
incrementing a version number, compiling a signed release, making sure the file
is really the fresh one, and sharing it. scripts/release-android.sh does all of
that in one command so nobody has to remember the sequence.
"Signed" means the app is stamped with a cryptographic key that proves it came from us; Android and Google Play refuse unsigned release builds. "rclone" is a command-line tool for copying files to cloud storage -here, Google Drive.
Quick reference
The common ways to run it. With no flags it bumps the build number, builds an APK, and uploads to Drive.
scripts/release-android.sh # bump +N, build APK, upload to Drive
scripts/release-android.sh --aab # build an app bundle (Play upload) instead
scripts/release-android.sh --no-bump # keep the current build number
scripts/release-android.sh --build-number 42 # set an explicit build number
scripts/release-android.sh --no-upload # build only, skip the Drive upload
These environment variables tune where and how it uploads:
| Env var | Default | Meaning |
|---|---|---|
SL_DRIVE_REMOTE | gdrive | rclone remote name to upload through |
SL_DRIVE_FOLDER | social live | Drive folder path (created on first upload if missing) |
SL_API_BASE_URL | (app default) | optional API_BASE_URL dart-define -point the build at a non-default backend (e.g. the test stack) |
SL_SIGNALING_URL | (app default) | optional SIGNALING_URL dart-define, pairs with SL_API_BASE_URL |
One-time setup
You only do this section once per machine. It sets up the signing key and the Google Drive connection the script relies on.
1. Upload keystore (release signing)
The keystore is the signing key file. Without it the build can't be signed for release, so the script refuses to run rather than produce a debug-signed app.
The release build is signed with the upload keystore configured in
android/key.properties (git-ignored). Without it the Gradle config falls
back to debug signing, so the script refuses to run.
# Generate the keystore once (keep the .jks and passwords in the password manager):
keytool -genkey -v -keystore ~/social-live-upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias upload
# Then create android/key.properties from the template:
cp android/key.properties.example android/key.properties
# ...and fill in storeFile / storePassword / keyPassword / keyAlias.
Never commit key.properties or the .jks file.
2. rclone → Google Drive auth
This connects rclone to your Google Drive so the script can upload. It opens a browser once to sign you in.
brew install rclone
rclone config
In the interactive config: n (new remote) → name it gdrive (or set
SL_DRIVE_REMOTE to whatever you choose) → storage type drive → accept the
defaults, leave client id/secret blank → scope drive (full access, needed to
create the upload folder and share links) → no service account → auto-config
y opens a browser for the Google sign-in. Verify with:
rclone lsd gdrive:
The token is stored in ~/.config/rclone/rclone.conf -per-machine, never in
the repo.
What the script does, step by step
For the curious or the debugging: here is exactly what happens on each run, so nothing is a black box.
- Preflight -fails fast if
flutteris missing,android/key.propertiesis absent (debug-signing guard), or (unless--no-upload) rclone or the configured remote is missing. - Bump -reads
version: X.Y.Z+Nfrompubspec.yamland rewrites it withN+1(or the value from--build-number). The+Nbuild number is shared with iOS: it must be unique per store upload on both platforms, so a bump here also "uses up" the number for the next iOS upload -that is intended, the two platforms stay on one counter.X.Y.Zis never touched; change it by hand when starting a new store version. - Build -
flutter build apk --release(default) orflutter build appbundle --releasewith--aab. Outputs:- APK:
build/app/outputs/flutter-apk/app-release.apk - AAB:
build/app/outputs/bundle/release/app-release.aab
- APK:
- Verify -asserts the artifact exists and was written after the build
started (a stale file from an earlier run fails the check), then copies it
to
build/social_live-vX.Y.Z-N.apk|aabso every upload is uniquely named. - Upload -
rclone copyto$SL_DRIVE_REMOTE:$SL_DRIVE_FOLDER, then lists the folder back to prove the file landed. rclone itself verifies size/checksum during copy. - Share link -best-effort
rclone link; prints an anyone-with-the-link URL for handing to testers. Non-fatal if the remote does not permit link creation.
Choosing APK vs AAB
Two output formats. Use an APK for direct install to testers; use an AAB only when you're feeding Google Play.
- APK (default): directly installable -right choice for Drive distribution to testers or sideloading onto devices.
- AAB (
--aab): what Google Play requires for store uploads. Not installable directly; use it when archiving the exact bundle sent to Play.
Troubleshooting
Common failures and their fixes.
android/key.properties missing-do the keystore setup above; the template isandroid/key.properties.example.rclone remote 'gdrive:' not configured-runrclone config; if you named the remote differently, exportSL_DRIVE_REMOTE=<name>.- rclone auth errors after a long idle period -the OAuth token can be
revoked; run
rclone config reconnect gdrive:to re-authorize. upload verification failed-the copy did not land (network drop or Drive quota). Re-run with--no-bumpso the build number is not consumed twice:scripts/release-android.sh --no-bump.- Build fails on signing -check the values in
android/key.propertiesand thatstoreFilepoints at an existing.jks; the Gradle wiring is inandroid/app/build.gradle.kts(signingConfigs). - Play rejects the build number -someone uploaded that
+Nalready; run again (the default bump picks the next number) or set--build-numberexplicitly.
Related pages
- Runbooks -the manual iOS and Android release procedures.
- Deployment & Operations -how server code (as opposed to the app) ships.
- Environment variables -backend settings a test-stack build might point at.