Android Release Checklist: Build, Sign, Verify, Ship

The debug build on your desk is not the build your users install. Minification, shrinking and signing all change behaviour, and the store review is an expensive place to discover it.

Before you build

  • Version bumped in one place, and versionCode strictly greater than the last upload.
  • Release keystore backed up somewhere you can still reach in five years. Losing it means you can never update that listing again.
  • No debug flags, no test API keys, no usesCleartextTraffic left enabled.
  • Privacy policy URL and data-safety answers updated for anything you started collecting this cycle.

Signing, and a keystore you can still open in five years

If you enrol in Play App Signing — and you should — Google holds the app signing key while you upload with a separate upload key. That split is what makes a mistake recoverable: a lost or leaked upload key can be reset through the console, whereas losing the app signing key without Play App Signing means abandoning the listing.

Advertisement
keytool -list -v -keystore release.jks -alias upload
./gradlew signingReport        # confirm the release variant is really signed

Keep the keystore and its passwords in two independent places — a secret manager and an offline copy — never in the repository, and never in a gradle.properties that ships with the project. Read the passwords from environment variables in CI, and write down who has access, because the failure here is organisational rather than technical.

versionCode is an integer you can never reuse

Play rejects an upload whose versionCode was used before — not just in the current track, ever. Keep it monotonic, generate it in one place, and do not reset it after a bad release. A readable scheme such as major * 10000 + minor * 100 + patch leaves plenty of headroom below the 2100000000 ceiling.

android {
  defaultConfig {
    versionCode = (System.getenv("BUILD_NUMBER") ?: "1").toInteger()
    versionName = "2.4.0"
  }
}

Tag the same commit in version control, so “which commit is build 20403?” has exactly one answer.

The release build itself

./gradlew clean bundleRelease
# Then verify what you are about to upload
bundletool build-apks --bundle=app/release/app-release.aab --output=app.apks
bundletool install-apks --apks=app.apks

Install the minified build on a physical device and click through the main flows. Reflective libraries, JSON parsers and anything relying on class names break here and nowhere else.

R8 rules, and what only breaks in a release build

The usual casualty of shrinking is reflection: a JSON library instantiating a model by name, a dependency reading a field dynamically, generated code that R8 renames because nothing keeps it.

-keep class com.example.app.model.** { *; }
-keepattributes Signature, InnerClasses, EnclosingMethod
-keepattributes SourceFile,LineNumberTable      # readable stack traces
-renamesourcefileattribute SourceFile           # hide internal paths

Every -keep line reduces what shrinking can remove, so keep the smallest surface that works — a blanket -keep class ** disables the feature you enabled. Test a build with minifyEnabled true and a real signing config rather than the debug variant, and upload the mapping.txt for that exact version: without it, a production stack trace is a wall of a.a.b with no line numbers.

Smoke test on the artifact, not the IDE

  • Cold start, and cold start after a reboot.
  • Sign in, sign out, sign in again — with the real backend, not a mock.
  • Offline: airplane mode on, open the app, then turn it back on. Does it recover without a restart?
  • Deep links, share sheet, and the notification tap path.
  • Permission dialogs: deny once, deny permanently, then grant from Settings.

After the upload

  • Deploy the updated privacy policy and delete-account page before the listing goes live; reviewers follow those links.
  • Keep the mapping file for the exact version you shipped — without it, a production stack trace is unreadable.
  • Watch the first day of crash reports and ANRs specifically, not the general dashboard.
  • Tag the release in version control so “what is in production” has a single answer.

Bundle or APK, and rolling out gradually

Upload an .aab and Play generates split APKs per ABI, screen density and language, so each device downloads only what it needs — commonly 15-30% smaller than a universal APK. The catch is that you cannot test a bundle by installing the bundle: build APKs from it, or use internal app sharing.

bundletool build-apks --bundle=app-release.aab --output=app.apks \
  --ks=release.jks --ks-key-alias=upload
bundletool install-apks --apks=app.apks      # add --mode=universal for one file

A staged rollout then starts at 5% or 20%, and it reaches new installs only — existing users stay on the old version until they update. Compare the crash-free rate per version for at least a day before increasing the percentage. Halting a rollout stops further distribution but does not downgrade anyone who already installed it; from that point the only fix is a higher versionCode.

Data safety, and the review answers that cause rejections

Most rejections are paperwork meeting code. The data safety form has to match what the app actually sends — an analytics SDK added this cycle, an advertising ID, a crash reporter with the user id attached. The privacy policy URL must be live and reachable before you submit, and an app that creates accounts needs an in-app deletion path as well as a web one.

Three more that catch people every year: declare the foreground service types you really use, request only permissions you can justify (QUERY_ALL_PACKAGES and exact alarms are both scrutinised), and build against the current target API level, since Google raises that requirement annually and stops accepting updates that miss it. Read the pre-launch report rather than skipping it — it runs your build on real devices and reliably surfaces small-screen layout problems and permission crashes that one test phone never shows.

None of this is clever. It is the difference between a release you control and a release that surprises you on a Sunday.

Advertisement
khallaf

Writing about programming, AI and the tools that make engineering teams faster. Published by A1 Systems.

Last updated 19 Sep 2026

// Keep reading

Related articles

Tools & Tricks 5 min read

Regex you will actually use

The small set of regex constructs that cover everyday work, the patterns worth keeping in a snippet file, and how to avoid catastrophic backtracking.

khallaf Tip