Techbase Academy
Android · Lesson 49

Prepare Your App for Release

14 phasesRelease Pipeline for Class RegisterAndroid Studio
Phase 1 of 14
Lesson Introduction

An app that runs on your emulator is not yet an app that people can install. Between "it works on my laptop" and "it is on Google Play" there is a set of steps: choose a permanent identity, set version numbers, shrink and protect your code, sign the app with a key only you hold, build an Android App Bundle, and test it with real users before the world sees it. Mistakes here are expensive. A lost signing key can mean you can never update your app, and a debug build uploaded by accident can leak secrets.

This lesson is the foundation for the Google Play Store Listing Certificate modules in lessons 50–53 and covers the Preparing and publishing an app material from the Android developer documentation (https://developer.android.com/studio/publish). It also follows the final steps of the Android Basics with Compose programme.

By the end you will be able to:

  • Explain the difference between debug and release builds.
  • Set and understand applicationId, versionCode and versionName.
  • Create an upload key, store it safely, and configure Gradle to sign a release build without putting passwords in source control.
  • Explain Play App Signing and why Google holds the app signing key.
  • Build an Android App Bundle (AAB) and explain how it differs from an APK.
  • Turn on R8 (shrinking, obfuscation and optimisation) and write keep rules.
  • Meet Google Play's target API level requirement.
  • Test with internal, closed, open tracks and read the pre-launch report.
  • Run through a release checklist.

Big idea: a release is a process, not a button. Make it repeatable, write it down, and never depend on one person's memory or one person's laptop.

✏️ Your Task
Practise what you just learned about Lesson Introduction. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 2 of 14
Prerequisite Concepts
  • Gradle files, Kotlin DSL and version catalogs (lessons 3 and 5).
  • Build variants debug and release (lesson 5).
  • Manifest permissions (lesson 29).
  • Running on a physical device (lesson 5).
  • Android Studio tools (lesson 44).
✏️ Your Task
Practise what you just learned about Prerequisite Concepts. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 3 of 14
Conceptual Understanding: The Journey from Code to Store
text
 source code
     |
     |  Gradle build (release variant)
     v
 compile -> R8 shrink/obfuscate/optimise -> package -> SIGN (upload key)
     |
     v
 app bundle (.aab)  ---- upload ---->  Google Play Console
                                            |
                                  Play re-signs with the app signing key
                                            |
                                  generates optimised APKs per device
                                            |
                                            v
                                     user's phone installs
Term Meaning
APK Android Package: the installable file. One APK contains everything for all devices
AAB (Android App Bundle) The publishing format you upload to Play. Google builds device-specific APKs from it, so downloads are smaller
Upload key The key you keep and use to sign the file you upload
App signing key The key Google Play uses to sign APKs delivered to users (managed by Play App Signing)
Keystore A file that holds one or more keys, protected by passwords
Alias The name of a key inside a keystore
Track A release channel in Play Console: internal, closed, open, production
✏️ Your Task
Practise what you just learned about Conceptual Understanding: The Journey from Code to Store. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 4 of 14
Step-by-Step Build 1: Application ID and Version Numbers

applicationId: your app's permanent identity

Open app/build.gradle.kts:

build.gradle.kts
android {
 namespace = "com.techbase.classregister"
 defaultConfig {
 applicationId = "com.techbase.classregister"
 minSdk = 24
 targetSdk = 36
 versionCode = 1
 versionName = "1.0.0"
 }
}

(Use the targetSdk and minSdk that Android Studio's current template chooses and that Google Play requires: see the target API section below. The numbers above are an example.)

  • namespace is the package used for the generated R class and your Kotlin code organisation. It is not what users see.
  • applicationId is the unique identifier of your app on the device and on Google Play. It must be unique across all of Google Play, uses lowercase letters, digits, underscores and dots, has at least two segments, and can never be changed after you publish, because a different ID means a different app. Choose it carefully, typically a reversed domain you control: com.techbase.classregister. Do not use com.example, which Google Play rejects.
  • If the namespace and applicationId differ, that is fine and common.

versionCode and versionName

Field Type Who sees it Rules
versionCode Integer Android and Play (not users) Must increase with every upload. 1, 2, 3... A higher number means newer. Maximum 2,100,000,000
versionName String Users Free text such as "1.0.0" or "2.3 beta"

Google Play rejects an upload whose versionCode is not higher than all previous uploads of that app on any track. A tidy approach is to derive the version from a single place, for example gradle.properties:

gradle.properties
APP_VERSION_CODE=7
APP_VERSION_NAME=1.2.0
build.gradle.kts
android {
 defaultConfig {
 versionCode = (project.property("APP_VERSION_CODE") as String).toInt()
 versionName = project.property("APP_VERSION_NAME") as String
 }
}

Use semantic versioning for versionName: MAJOR.MINOR.PATCH. Increase MAJOR for big or breaking changes, MINOR for new features, PATCH for fixes. Bigger teams generate versionCode from the CI build number so that it increases automatically.

The app name and icon

Set android:label="@string/app_name" in the manifest, and make a proper adaptive icon (lesson 21, Image Asset Studio). Check the name looks right on a home screen where it is truncated to roughly 12 characters.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 1: Application ID and Version Numbers. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 5 of 14
Step-by-Step Build 2: Build Types and What Release Changes

Gradle has two default build types. You can add more (for example staging).

build.gradle.kts
android {
 buildTypes {
 debug {
 applicationIdSuffix = ".debug"
 versionNameSuffix = "-debug"
 isDebuggable = true
 }
 release {
 isMinifyEnabled = true
 isShrinkResources = true
 proguardFiles(
 getDefaultProguardFile("proguard-android-optimize.txt"),
 "proguard-rules.pro"
 )
 // signingConfig is added in the signing section below
 }
 }
}
Setting Debug Release
Debuggable Yes No
Code shrinking / obfuscation (R8) Off On
Signed with An automatic debug key Your upload key
Logging Verbose Minimal; remove sensitive logs
Server URLs / API keys Test Production
Performance Slower Optimised

applicationIdSuffix = ".debug" lets a debug and a release build be installed on the same phone at the same time because they have different IDs. Use BuildConfig fields (enable with buildFeatures { buildConfig = true }) and per-build-type resources to switch environments, not if (BuildConfig.DEBUG) scattered through code.

Never ship a debuggable build. Play rejects an uploaded bundle with debuggable=true, and it is a security risk.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 2: Build Types and What Release Changes. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 6 of 14
Step-by-Step Build 3: Signing

Why apps are signed

Android requires every installed app to be digitally signed. The signature proves two things: the update came from the same publisher as the original, and the file was not changed after signing. Lose the key, or let someone steal it, and you lose control of your app.

Play App Signing: the modern model

With Play App Signing (required for new apps on Google Play):

  1. Google holds the app signing key in its secure infrastructure and signs the APKs delivered to users.
  2. You create an upload key and use it to sign your AAB before upload. Google verifies the upload signature, strips it and re-signs with the app signing key.
  3. If you lose your upload key, you can request a reset from Google, because the app signing key (which users' phones trust) is safe. In the old model, losing the only key meant you could never update the app again.

Even so: guard the upload key carefully, because anyone with it could upload an update to your app (if they also have Console access).

Create an upload keystore in Android Studio

  1. Build → Generate Signed App Bundle / APK…
  2. Choose Android App Bundle, then Next.
  3. Under Key store path click Create new….
  4. Choose a location outside your project folder (for example ~/keys/techbase-upload.jks), set a strong keystore password, an alias (for example upload), a key password, a validity of at least 25 years (Google requires the key to be valid beyond October 2033; 25+ years is the usual guidance) and fill the certificate fields (name, organisation, country code NG).
  5. Click OK, then continue the wizard.

Or from the command line (the keytool that ships with the JDK):

bash
keytool -genkeypair -v \
 -keystore ~/keys/techbase-upload.jks \
 -alias upload \
 -keyalg RSA -keysize 2048 \
 -validity 10000

-validity 10000 is in days (about 27 years). The tool asks for passwords and details.

Back up the keystore and write the passwords in a password manager, in at least two secure places (for example an encrypted drive and your organisation's password manager). Never commit the keystore or its passwords to Git. Add *.jks, *.keystore and keystore.properties to .gitignore.

Configure Gradle to sign without exposing secrets

Create a file keystore.properties in the project root (and add it to .gitignore):

keystore.properties
storeFile=/home/you/keys/techbase-upload.jks
storePassword=YOUR_STORE_PASSWORD
keyAlias=upload
keyPassword=YOUR_KEY_PASSWORD

Then read it in app/build.gradle.kts:

build.gradle.kts
import java.io.FileInputStream
import java.util.Properties
val keystoreProperties = Properties().apply {
 val file = rootProject.file("keystore.properties")
 if (file.exists()) load(FileInputStream(file))
}
android {
 signingConfigs {
 create("release") {
 if (keystoreProperties.isNotEmpty()) {
 storeFile = file(keystoreProperties.getProperty("storeFile"))
 storePassword = keystoreProperties.getProperty("storePassword")
 keyAlias = keystoreProperties.getProperty("keyAlias")
 keyPassword = keystoreProperties.getProperty("keyPassword")
 }
 }
 }
 buildTypes {
 release {
 signingConfig = signingConfigs.getByName("release")
 isMinifyEnabled = true
 isShrinkResources = true
 proguardFiles(
 getDefaultProguardFile("proguard-android-optimize.txt"),
 "proguard-rules.pro"
 )
 }
 }
}

The if (keystoreProperties.isNotEmpty()) guard lets teammates and CI jobs without the key still sync the project (they just cannot produce a signed release). In CI, supply the same values through encrypted secrets and write the file at build time, or read environment variables (System.getenv("STORE_PASSWORD")).

✏️ Your Task
Practise what you just learned about Step-by-Step Build 3: Signing. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 7 of 14
Step-by-Step Build 4: App Bundles and Testing the Release Build

Build the bundle

bash
./gradlew bundleRelease

The output is app/build/outputs/bundle/release/app-release.aab. For a quick on-device APK test of the release variant:

bash
./gradlew assembleRelease

producing app/build/outputs/apk/release/app-release.apk (signed with your release config). In Android Studio use the Build Variants panel to switch the active variant to release and run it on a device, which is the correct way to test what users will really get.

Why AAB?

An APK must contain resources for every screen density, every CPU architecture and every language. An App Bundle contains all of them separately, and Play assembles a split APK set just for each user's phone. Typical results: noticeably smaller downloads and installs, which matters a great deal for users on limited data plans or low-storage phones. AAB is required for new apps on Google Play.

You can test how Play will split your bundle locally with bundletool (a command-line tool from Google):

bash
# Build a set of APKs for a connected device from the bundle
java -jar bundletool.jar build-apks \
 --bundle=app/build/outputs/bundle/release/app-release.aab \
 --output=app.apks \
 --ks=/home/you/keys/techbase-upload.jks --ks-key-alias=upload

# Install exactly what that device would receive
java -jar bundletool.jar install-apks --apks=app.apks

(Download bundletool from its official GitHub releases page; the exact command options are documented there.)

Inspect the final APK

Build → Analyze APK… opens a release APK or AAB and shows what takes space: classes.dex, resources, native libraries, assets. Large images and unused libraries are the common culprits. Consider WebP images, vector drawables and removing unused dependencies.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 4: App Bundles and Testing the Release Build. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 8 of 14
Step-by-Step Build 5: R8 Shrinking, Obfuscation and Keep Rules

isMinifyEnabled = true turns on R8, Android's code shrinker. It does three things:

Feature What it does
Shrinking Removes classes and methods your app never uses
Obfuscation Renames classes and members to short names (a, b) to make reverse engineering harder and code smaller
Optimisation Rewrites code for speed (inlining and more)
isShrinkResources = true Also removes unused resources (layouts, images, strings)

Because R8 analyses code statically, it can remove or rename things your app uses indirectly (via reflection, JSON field names, native code). The usual symptoms: the release app crashes with ClassNotFoundException or NoSuchMethodException, or JSON parsing silently returns empty fields, while the debug app works. The cure is a keep rule in proguard-rules.pro:

proguard-rules.pro
# Keep the data classes used with Gson/Moshi reflection
-keep class com.techbase.classregister.data.remote.dto.** { *; }

# Keep enum names used in serialisation
-keepclassmembers enum * { *; }

# Keep line numbers in stack traces (helps Play crash reports)
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

Good news: modern libraries (Room, Retrofit with kotlinx.serialization, Hilt, WorkManager, Compose) ship their own consumer rules and need little or none from you. Most keep rules you will write are for your own classes accessed by reflection. Always read the library's documentation for required rules and test the release build thoroughly.

Mapping file

R8 produces a mapping file (app/build/outputs/mapping/release/mapping.txt) that translates obfuscated stack traces back to readable names. When you upload a bundle built with R8, Android Studio's bundle and Play Console accept the mapping automatically in modern setups (it is included in the bundle's metadata); keep a copy of the mapping file for each released version anyway, since you cannot reconstruct it.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 5: R8 Shrinking, Obfuscation and Keep Rules. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 9 of 14
Step-by-Step Build 6: Policy, Target API and Other Requirements

Google Play has rules that change, so always read the current Help pages.

Target API level

At the time of writing (October 2026), the official page states that from August 31, 2026, new apps and app updates must target Android 16 (API level 36) or higher (with lower requirements for Wear OS, Automotive, TV and XR), and existing apps must target at least Android 15 (API 35) to stay available to new users on newer devices. A one-time extension to November 1, 2026 can be requested in Play Console. Check the current page before every release: https://developer.android.com/google/play/requirements/target-sdk.

In Gradle, targetSdk tells Android which behaviour changes your app has been tested against; compileSdk is the SDK you compile with (should be at least targetSdk); minSdk is the oldest Android version you support. Raising targetSdk opts you into new behaviour (for example edge-to-edge, notification and background restrictions), so read the behaviour changes for each API level and test.

Other items to check before publishing

  • Permissions: request only what you need; explain why (lesson 29 for INTERNET). Sensitive permissions (SMS, call logs, background location) need a Play Console declaration and may be refused.
  • Data safety form and privacy policy: describe what data you collect and share. If your app handles personal data you need a privacy policy URL.
  • Content rating questionnaire: completed in Play Console.
  • Ads, in-app purchases, subscriptions: policies apply.
  • Family and children's apps: stricter rules.
  • App access: if reviewers need a login, provide test credentials.
  • 64-bit and 16 KB page-size requirements: native libraries must meet current requirements. If your app has no native code (pure Kotlin/Compose) this is automatic; check the documentation if you include native libraries.
  • Developer verification: Google is introducing verification requirements for developers; check the current Play and Android developer-verification announcements.
✏️ Your Task
Practise what you just learned about Step-by-Step Build 6: Policy, Target API and Other Requirements. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 10 of 14
Step-by-Step Build 7: Testing Tracks and the Pre-Launch Report

Never go straight from your laptop to everyone. Play Console offers tracks:

Track Audience Use it for
Internal testing Up to 100 testers you list by email Quick checks of every build; available within minutes
Closed testing Invited testers (email lists or Google Groups) Beta with friends, classmates, a client
Open testing Anyone can join from the store listing Public beta
Production Everyone, optionally with staged rollout (for example 10% of users) The real release

New personal developer accounts (created after November 13, 2023) must run a closed test with at least 12 testers opted in continuously for 14 days before they can apply for production access, according to Play Console Help at the time of writing. Organisation accounts are treated differently. Check the current rule in Help before planning your launch date.

The pre-launch report

When you upload to a testing track, Google can automatically install your app on a set of real devices and crawl it, then produce a pre-launch report with crashes, performance, accessibility checks (the Accessibility Scanner-style findings of lesson 48), security vulnerabilities and screenshots of devices. Read it before you promote a release.

Staged rollout and monitoring

In production choose a staged rollout percentage, watch Android vitals (crash rate, ANR rate) and reviews, then increase. If something is wrong, halt the rollout. You cannot "un-publish" an update for users who already have it, so you need to release a fix with a higher versionCode.

✏️ Your Task
Practise what you just learned about Step-by-Step Build 7: Testing Tracks and the Pre-Launch Report. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 11 of 14
Step-by-Step Build 8: The Release Checklist

Copy this into your repository as RELEASE.md:

text
BEFORE BUILD
[ ] applicationId final and unique (not com.example)
[ ] versionCode increased; versionName updated; changelog written
[ ] targetSdk meets the current Play requirement; tested on that API
[ ] minSdk decision documented
[ ] All debug code, test endpoints and logs with sensitive data removed
[ ] API keys not hard-coded in source (use secrets plugin or server-side keys)
[ ] Permissions reviewed; unused ones removed
[ ] Strings translated or reviewed; no placeholder text

BUILD
[ ] Release variant selected; isMinifyEnabled and isShrinkResources true
[ ] keystore.properties present; keystore NOT in version control
[ ] ./gradlew clean bundleRelease succeeds
[ ] Release APK tested on at least 2 real devices (one low-end)
[ ] Core flows tested after R8 (login, save, network, rotate, back)
[ ] mapping.txt archived with the version

PLAY CONSOLE
[ ] Upload AAB to internal track first
[ ] Data safety, content rating, privacy policy and app access complete
[ ] Pre-launch report reviewed
[ ] Store listing complete (lessons 50-53)
[ ] Staged rollout configured

AFTER
[ ] Tag the commit: git tag v1.2.0
[ ] Watch Android vitals and reviews for 48 hours
✏️ Your Task
Practise what you just learned about Step-by-Step Build 8: The Release Checklist. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 12 of 14
Guided Practice Exercises
🎯 Your Challenge

Exercise 1: Make a release build

Take any project from this course (for example the Class Register or Naija Snacks). Choose a real applicationId, set version 1/1.0.0, create an upload keystore outside the project, add keystore.properties to .gitignore, configure the signing, and produce a signed app-release.aab. List the commands and files involved.

Steps and artefacts:

  1. applicationId = "com.yourname.classregister", versionCode = 1, versionName = "1.0.0" in defaultConfig.
  2. keytool -genkeypair -v -keystore ~/keys/upload.jks -alias upload -keyalg RSA -keysize 2048 -validity 10000.
  3. Create keystore.properties at the root with storeFile, storePassword, keyAlias and keyPassword; add these lines to .gitignore:
text
keystore.properties
*.jks
*.keystore
  1. Add the signingConfigs.create("release") { ... } block reading the properties and set signingConfig = signingConfigs.getByName("release") in the release build type.
  2. ./gradlew clean bundleRelease.
  3. Find app/build/outputs/bundle/release/app-release.aab.
  4. Check with git status that no key file or properties file appears as untracked.
🎯 Your Challenge

Exercise 2: Fix a release-only crash

Your debug app parses JSON into a data class Student(val name: String, val score: Int) using a reflection-based library. The release build shows empty names. Explain why, and give two fixes.

R8 renamed the fields of Student (name became a), so reflection-based JSON mapping no longer finds the JSON keys. Fix 1: add a keep rule for the data classes, such as -keep class com.yourname.app.data.remote.dto.** { *; }. Fix 2: use a serialisation approach that does not rely on field names at runtime: kotlinx.serialization with @Serializable generates code at compile time (and annotate names with @SerialName), so R8 can safely rename the fields. Then re-test the release build, not only the debug build.

🎯 Your Challenge

Exercise 3: Version numbers

Your last upload was versionCode = 12, versionName = "1.3.0". You fix a crash. What should the new values be, and what happens if you upload versionCode = 12 again?

New values: versionCode = 13 (any higher integer) and versionName = "1.3.1" (a patch release). Uploading versionCode = 12 again is rejected by Play Console with an error saying the version code has already been used (or is not higher than the existing one); versionCode must increase on every upload to any track.

✏️ Your Task
Practise what you just learned about Guided Practice Exercises. Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 13 of 14
Common Mistakes (and How to Fix Them)

Mistake #1: Keeping com.example as the application ID. Symptom: Play rejects the upload. Fix: choose a real, unique ID before the first release.

Mistake #2: Committing the keystore or passwords. Symptom: anyone with repository access can sign as you. Fix: .gitignore, secrets in CI, rotate if leaked (upload key reset through Play Console).

Mistake #3: Losing the only copy of the keystore. Fix: back up; Play App Signing allows an upload-key reset, but it takes time.

Mistake #4: Forgetting to raise versionCode. Symptom: upload rejected. Fix: automate it.

Mistake #5: Only testing the debug build. Symptom: release-only R8 crashes. Fix: test the release variant on real devices.

Mistake #6: Uploading an APK instead of an AAB. Symptom: not accepted for new apps. Fix: bundleRelease.

Mistake #7: Hard-coding API keys in source. Symptom: extractable from the APK. Fix: keep real secrets on a server; for client-side keys, use restrictions (package name and signing certificate) in the provider's console.

Mistake #8: Ignoring the target API deadline. Symptom: cannot publish updates. Fix: track the requirement and test on the new API early.

Mistake #9: Shipping with logging of personal data. Fix: strip or avoid sensitive logs.

Mistake #10: Releasing to 100% immediately. Fix: internal, closed, then staged production rollout.

✏️ Your Task
Practise what you just learned about Common Mistakes (and How to Fix Them). Open Android Studio, type the examples by hand, change a value, and observe what happens.
Phase 14 of 14
Knowledge Check

Question 1. What is the difference between versionCode and versionName?

versionCode is an integer used by Android and Play to order versions; it must increase with each upload. versionName is a human-readable string shown to users, such as "1.3.1".

Question 2. What does Play App Signing do?

Google stores and uses the app signing key to sign the APKs delivered to users, while you sign uploads with a separate upload key that can be reset if lost.

Question 3. Why does the release build sometimes crash when the debug build does not?

R8 shrinks and obfuscates code in release builds and can remove or rename classes and fields used through reflection. Missing keep rules cause crashes or missing data. Debug builds do not run R8.

Question 4. What is an Android App Bundle and why is it used?

It is the publishing format that contains all code and resources; Google Play builds optimised, smaller APKs for each user's device from it, so downloads are smaller.

Question 5. Why use testing tracks before production?

They let you check a build with limited audiences and automated tests (the pre-launch report) and fix problems before everyone sees it, and a staged production rollout limits the damage of any remaining bug.

Go deeper: official resources

✏️ Your Task
Practise what you just learned about Knowledge Check. Open Android Studio, type the examples by hand, change a value, and observe what happens.
🏗️ Build It — Mini Project
Release Pipeline for Class Register

Project goal: Take the Class Register (or any app you built) from debug-only to a repeatable, signed, shrunk release, with documentation another developer could follow.

Requirements:

  1. A real applicationId, versionCode/versionName driven from gradle.properties.
  2. An upload keystore outside the repo, keystore.properties ignored by Git, and a signing config that tolerates its absence.
  3. release with R8 and resource shrinking, and a proguard-rules.pro with at least the line-number rules.
  4. A debug build type with an applicationIdSuffix so both builds can be installed together.
  5. ./gradlew bundleRelease produces a signed AAB; a release APK built with assembleRelease is installed and the core flows tested.
  6. A RELEASE.md checklist in the repo.
  7. Record in a short note: AAB size, APK size and what Analyze APK showed as the biggest item.

Stretch goal: write a GitHub Actions workflow that builds the bundle in CI using encrypted secrets for the keystore (base64-encoded and decoded at build time).

gradle.properties additions:

gradle.properties
APP_VERSION_CODE=1
APP_VERSION_NAME=1.0.0

app/build.gradle.kts (key parts):

build.gradle.kts
import java.io.FileInputStream
import java.util.Properties
plugins {
 alias(libs.plugins.android.application)
 alias(libs.plugins.kotlin.android)
 alias(libs.plugins.kotlin.compose)
}
val keystoreProperties = Properties().apply {
 val f = rootProject.file("keystore.properties")
 if (f.exists()) load(FileInputStream(f))
}
android {
 namespace = "com.yourname.classregister"
 compileSdk = 36
 defaultConfig {
 applicationId = "com.yourname.classregister"
 minSdk = 24
 targetSdk = 36
 versionCode = (project.property("APP_VERSION_CODE") as String).toInt()
 versionName = project.property("APP_VERSION_NAME") as String
 }
 signingConfigs {
 create("release") {
 if (keystoreProperties.isNotEmpty()) {
 storeFile = file(keystoreProperties.getProperty("storeFile"))
 storePassword = keystoreProperties.getProperty("storePassword")
 keyAlias = keystoreProperties.getProperty("keyAlias")
 keyPassword = keystoreProperties.getProperty("keyPassword")
 }
 }
 }
 buildTypes {
 debug {
 applicationIdSuffix = ".debug"
 versionNameSuffix = "-debug"
 }
 release {
 isMinifyEnabled = true
 isShrinkResources = true
 signingConfig = signingConfigs.getByName("release")
 proguardFiles(
 getDefaultProguardFile("proguard-android-optimize.txt"),
 "proguard-rules.pro"
 )
 }
 }
}

.gitignore lines:

text
keystore.properties
*.jks
*.keystore
local.properties

proguard-rules.pro:

proguard-rules.pro
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile

Commands:

bash
./gradlew clean bundleRelease
./gradlew assembleRelease
adb install -r app/build/outputs/apk/release/app-release.apk

Optional CI workflow (.github/workflows/release.yml):

yaml
name: Release bundle
on:
 push:
 tags: ["v*"]
jobs:
 bundle:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - uses: actions/setup-java@v4
 with:
 distribution: temurin
 java-version: "17"
 - name: Restore keystore
 run: |
 echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/upload.jks"
 cat > keystore.properties <<EOF
 storeFile=$RUNNER_TEMP/upload.jks
 storePassword=${{ secrets.KEYSTORE_PASSWORD }}
 keyAlias=${{ secrets.KEY_ALIAS }}
 keyPassword=${{ secrets.KEY_PASSWORD }}
 EOF
 - run: ./gradlew bundleRelease
 - uses: actions/upload-artifact@v4
 with:
 name: aab
 path: app/build/outputs/bundle/release/*.aab

Check the current versions of the actions and the Java version your Android Gradle Plugin requires. Expected result: the signed .aab appears in app/build/outputs/bundle/release/, the release APK installs next to the debug build (different IDs), and nothing secret is tracked by Git. Your note should record the two sizes and the largest item from Analyze APK (commonly classes.dex or images).

1

Open your finished project in Android Studio. Check that the .gitignore file the wizard created is in the project root. It keeps local.properties, build folders and keystores out of Git. Never commit passwords or signing keys.

2

Choose Git > Create Git Repository… (or VCS > Enable Version Control Integration in older versions), select the project folder, then open Git > Commit, tick every file, and write the message “Add Android lesson 49 project”.

3

On github.com click New repository, name it android-lesson-49-prepare-your-app-for-release, set it to Public, leave Add a README unticked, and click Create repository.

4

Back in Android Studio choose Git > Manage Remotes…, add the repository URL as origin, then choose Git > Push… and push your main branch. Prefer the terminal? Run git remote add origin <your-url> then git push -u origin main.

5

Add a README.md with a screenshot of your app running and a short description. Employers and clients read READMEs first. Your project is now part of your public portfolio. 🎉

Lesson 49 complete! 🎉

You covered:

  • Lesson Introduction
  • Prerequisite Concepts
  • Conceptual Understanding: The Journey from Code to Store
  • Step-by-Step Build 1: Application ID and Version Numbers
  • Step-by-Step Build 2: Build Types and What Release Changes
  • Step-by-Step Build 3: Signing
  • Step-by-Step Build 4: App Bundles and Testing the Release Build
  • Step-by-Step Build 5: R8 Shrinking, Obfuscation and Keep Rules
  • Step-by-Step Build 6: Policy, Target API and Other Requirements
  • Step-by-Step Build 7: Testing Tracks and the Pre-Launch Report
  • Step-by-Step Build 8: The Release Checklist
  • Guided Practice Exercises
  • Common Mistakes (and How to Fix Them)
  • Knowledge Check
All 72 lessons in Google Android Studio ·Techbase Academy ·© 2025–2026 Techbase Consultant Services, Ibadan
Esc
↑ ↓ to moveEnter to openEsc to close