Turn your project into a shippable app — build variants for debug and release, a cryptographically signed release bundle, and the path to the Play Store (plus Firebase App Distribution for testers).
Gradle builds your app in variants — most importantly a debug build for development (debuggable, no optimization) and a release build for shipping (optimized, shrunk, minified). Build types let you point debug and release at different servers or feature flags from one codebase. Understanding variants keeps development conveniences out of your production app.
// app/build.gradle.kts
android {
buildTypes {
debug {
applicationIdSuffix = ".debug" // install alongside release
isDebuggable = true
}
release {
isMinifyEnabled = true // shrink + obfuscate with R8
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
// point at the production API, disable logging, etc.
}
}
}
// build the release: ./gradlew assembleRelease (or bundleRelease for an AAB)Every Android app must be cryptographically signed; the signature identifies you as the author and Android will only install updates signed with the same key. You keep this signing key secret and safe forever — lose it and you can never update your app under the same listing. Modern releases use Play App Signing, where Google manages the final key while you hold an upload key.
Guard your key required — The signing (upload) key is irreplaceable. If you lose it or it leaks, you can no longer publish updates to the same app listing. Back it up securely and never commit it to source control.// configure signing (keep the keystore + passwords OUT of git!)
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("KEYSTORE_PATH"))
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = "upload"
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes { getByName("release") { signingConfig = signingConfigs.getByName("release") } }
}
// generate an upload key once:
// keytool -genkey -v -keystore upload.jks -keyalg RSA -keysize 2048 -alias upload
// LOSE THIS KEY = you can never update the app. Back it up.You distribute through the Google Play Console: upload a signed Android App Bundle (AAB), which lets Google generate optimized APKs per device, complete the store listing and content rating, and roll out. Staged rollouts (release to 10% first) and testing tracks (internal, closed, open) let you catch problems before everyone updates. This is the standard path to users.
Play Store release flow:
1. build a signed AAB ./gradlew bundleRelease (app-release.aab)
2. Play Console: create the app + store listing (screenshots, description)
3. content rating + data safety form (required)
4. upload the AAB to a track:
internal -> your team, instant
closed -> invited testers
open -> public beta
production -> everyone
5. STAGED ROLLOUT: release to 10% -> watch crash rate -> ramp to 100%
Google generates per-device APKs from your AAB (smaller downloads). Bump
versionCode every upload.Before the Play Store, you often need to get builds to testers fast. Firebase App Distribution sends a signed build directly to a list of testers who install it without the Play Store — ideal for QA and stakeholder review during development. It complements the Play Store’s testing tracks and is commonly wired into CI so every merge produces a testable build.
# distribute a build to testers directly (outside the Play Store)
# via the Firebase CLI or Gradle plugin:
./gradlew appDistributionUploadRelease
# testers get an email/link and install the signed build on their device.
# great for fast QA + stakeholder review during development.
# wire it into CI (see the GitHub course) so every merge -> a testable build.
# Play Store testing TRACKS (internal/closed/open) do a similar job through
# Google Play; teams often use Firebase for speed pre-release, Play tracks for beta.