Debug vs release builds
Every app you have built so far was a debug build. There is a second kind, and it is a different app in almost every way that matters — smaller, faster, harder to inspect, and impossible to install until you have signed it. Here is exactly what changes.
You have only ever built half the story
Count the number of times you have pressed Run since Lesson 0.3. Dozens, probably a few hundred. Every single one of those produced the same kind of file: a .
There is another kind. It comes from the same source code, the same and the same phone, and it is a noticeably different app when it lands. It is smaller. It starts faster. Its internals are unreadable. And out of the box, no phone on earth will install it.
That second kind is the , and this lesson is the whole of the difference.
Think about a play, on two different nights.
Rehearsal night. The house lights are up so people can see. Actors are holding scripts. Half the set is still on scaffolding, and there is bare plywood where the painted flats will go. Anybody can shout "stop" and the whole thing pauses while a problem gets sorted out. It is deliberately easy to interrupt.
Opening night. Lights down. No scripts. The scaffolding is gone and so is every prop that turned out not to be used. Nobody shouts stop. And on the door there is a programme with the theatre's name printed on it, because the audience is entitled to know who is responsible for what they are about to watch.
Same play. Same words. Two completely different evenings.
A debug build is the rehearsal: interruptible on purpose, nothing thrown away yet, and the work lights left on. A release build is opening night: stripped down, sealed up, and stamped with your name.
Where the two builds come from
Both are already in your project. You did not add them and you cannot remove them — every Android project starts with exactly two called debug and release.
Here is the real buildTypes block out of Dice Duel, unedited:
The four things a debug build does for you
Every one of these is a favour, and every one of them has to be undone before anyone else sees your app.
1. It signs itself. Android refuses to install an unsigned app — there is no such thing as an install without a signature. So Android generates a throwaway key the first time you build anything, keeps it in a file called the , and quietly signs every debug build with it. That is why pressing Run has never once asked you about keys.
2. It is . The finished app carries a flag saying "another program is allowed to attach to me and look inside". That is what makes a possible, and it is exactly what you would never want in an app on a stranger's phone.
3. It skips the shrinking. No , no , no deleting. Every class you compiled is in there, under its real name, including the thousands of Compose classes your one screen never touches. That is why the Dice Duel debug APK is about 8.7 MB for a game with one screen and one die.
4. It is fast to build. All of the above adds up: nothing to analyse, nothing to rewrite, no key to check. On a phone, that difference is minutes, not seconds.
Focus Flow, built both ways in Pocket Studio on the same device:
| Build | Size | Build time |
|---|---|---|
app-debug.apk | 19.58 MB | 270.8 s from cold |
app-release.apk | 11.50 MB | 136.0 s |
The release APK is 41% smaller — and here is the part worth pausing on: Focus Flow ships with isMinifyEnabled = false, so never ran. Not one class was deleted or renamed.
All of that saving came from the other favours being withdrawn: no debug metadata, no debuggable flag, and native libraries getting their debug symbols stripped instead of shipped whole. Shrinking is a separate saving on top, and we turn it on later in this lesson.
If you take one thing from this table, make it that "smaller" and "shrunk" are not the same word.
The four things a release build does instead
Take the favours away and you get the other build.
| Behaviour | Debug | Release |
|---|---|---|
| Signed with | An automatic throwaway key | Your own key, which you make |
| Can attach a debugger | Yes | No |
| Unused code removed | No | Yes, when minifying is on |
| Names in the app | Real | Shortened and meaningless |
| Extra checks | None | Lint's fatal checks must pass |
| Installable straight away | Yes | Not until it is signed |
The last row is the one that surprises people. Build a release APK today, with no key set up, and the build succeeds. It just hands you a file with a name that tells you exactly what is wrong with it:
1app/build/outputs/apk/debug/app-debug.apk
2app/build/outputs/apk/release/app-release-unsigned.apkAn is a complete, finished app. Every class, every picture, every string. Android will still not touch it. Lesson 6.6 is about fixing that.
The outputs folder in the Pocket Studio file tree, after building both types.
Turning R8 on
is the shrinker. On a release build it walks your entire program starting from the entry points, works out which code can possibly be reached, and deletes the rest. Then it renames what survives: GameViewModel.rollDice() becomes something like a.b(), which is shorter to store and impossible to skim.
Two settings switch it on, and a third tells it where your rules live:
1buildTypes {
2 release {
3 isMinifyEnabled = true
4 isShrinkResources = true
5 proguardFiles(
6 getDefaultProguardFile(
7 "proguard-android-optimize.txt"
8 ),
9 "proguard-rules.pro"
10 )
11 }
12}- runs R8 over your code.
- does the same job for pictures, strings and layouts that nothing refers to. It only works when minifying is already on.
getDefaultProguardFile(...)pulls in the rules Google ships for you — the ones that stop R8 breaking Android itself.proguard-rules.prois your own file of , sitting in theappfolder. Create it empty; you add to it only when something breaks.
These three apps ship with isMinifyEnabled = false, so this course has never measured a shrunk build of them. For a Compose app of this size, R8 usually removes well over half the file. What it definitely does is make a crash report unreadable, which is why R8 writes a every time it runs. Keep the one that matches each published version and you can translate a.b() back into GameViewModel.rollDice() later.
R8 deletes what nothing calls. But some code is not called — it is found by name at run time, by a library reading a string. R8 cannot see that connection, so it deletes the class, and your app crashes in release and works perfectly in debug.
The fix is always a keep rule in proguard-rules.pro. Modern libraries ship their own rules, so this is rarer than it used to be — but if release crashes and debug does not, this is where you look first.
The task names
You already know assembleDebug from Lesson 2.5. Its siblings do the obvious thing:
1:app:assembleDebug -> app-debug.apk
2:app:assembleRelease -> app-release-unsigned.apk
3:app:bundleRelease -> app-release.aabThe third one produces an rather than an APK. That is the format Google Play wants, and Lesson 6.7 explains why.
What this costs you: nothing
Worth saying plainly, because this is the lesson where the two worlds meet.
Every build in this course is a debug build. Part 0 through to the end of Part 6, all three apps, every checkpoint — debug, all of it. Debug APKs are what the Pocket Studio Free tier produces, and they are all you need to finish everything here and keep building your own apps afterwards.
The Free tier caps how many builds you get. That is the only limit you have run into, and it is why this course keeps telling you to read your change before you press Run. Nothing in Part 6 asks you to pay to carry on learning.
Look at both build types in your own project. Use Focus Flow, or any project you like.
- Open Pocket Studio, tap Projects, open Focus Flow.
- Tap Editor, open the file tree, open
app/build.gradle.kts. - Find the
buildTypesblock. Confirm for yourself that there is nodebugblock in it. - Change
isMinifyEnabledfromfalsetotrue, then add the two lines under it and theproguardFiles(...)call exactly as shown above. - Long-press the
appfolder in the file tree, choose New File, and name itproguard-rules.pro. Leave it completely empty. That is a valid rules file. - Tap Build, then press Run. It builds and installs exactly as normal — because Run builds
debug, and not one line of what you just typed applies todebug. - Tap Editor and open
app/build/outputs/apk/debug/. There isapp-debug.apk, freshly rebuilt. Note its size. - Look for a
releasefolder beside it. There is not one, because you have never built that type. That empty space is what the next two lessons are for. - If your Pocket Studio tier offers release builds, watch one happen: open the Terminal and run
./gradlew :app:assembleRelease. Read the output rather than skimming it, and look for:app:minifyReleaseWithR8— that task does not exist in a debug build at all. The Free tier's output is debug APKs, so if Pocket Studio stops you here, that is the honest boundary, and nothing later in the course depends on crossing it. - Leave
isMinifyEnabled = truein place, or set it back tofalse. Either way, Run goes on building and installing the debug APK exactly as it always has.
versionCode, a resource that will crash on an old phone.lintVitalRelease only fires on things that genuinely break released apps.build/outputs/mapping/release/missing_rules.txt. R8 has already written the exact rules you need. Copy them into app/proguard-rules.pro and build again.applicationId, different key. Android treats those as two different apps by two different people, and will never let one replace the other.gradle.properties in the project root and raise the number in org.gradle.jvmargs=-Xmx2048m to -Xmx3072m. Close your other apps first, or the phone will simply refuse to hand that memory over.- Every project has two from the start.
debugneeds no block in your build file;releaseis the one you configure. - A signs itself with the shared , stays , keeps every class, and builds fast. All four are favours for testing.
- A does none of that.
assembleReleasewith no key producesapp-release-unsigned.apk— a finished app no phone will install. - turns on, which deletes unreachable code and renames what survives. Keep the it writes.
- Release builds also run 's fatal checks, so a build can fail in release that has been green in debug for weeks.
- The whole of this course runs on debug builds, which the Pocket Studio Free tier produces. Nothing here asks you to pay to keep learning.
- Next: the missing piece — what a signature actually proves, how to make a key of your own, and the one file you must never lose.