ProGuard / R8

Every release build of TrailLog ships as a single APK or app bundle containing every class from every library it depends on, with the exact class and method names the compiler produced — names like TrailRepository or getTrailsSortedByDate that are perfectly readable to anyone who unzips the file. R8 is Android's build-time tool for turning that readable, bulky output into something smaller, faster, and harder to casually read — and it runs automatically as part of a release build once you switch it on.

R8 and ProGuard: Why the Names Get Confused

ProGuard was the original standalone Java shrinker/obfuscator tool Android projects used for years. R8 is Google's own replacement — it does the same job, faster, as part of the Android Gradle Plugin itself, and it has fully replaced ProGuard as the tool that actually runs in every modern Android project. Here's the part that genuinely confuses people: R8 still reads configuration files written in ProGuard's rule syntax, and Android Studio still names that file proguard-rules.pro by convention. So when you see "ProGuard rules" mentioned anywhere in this lesson (or anywhere online), it almost always means rules fed into R8, not the old standalone tool — the file format outlived the tool that invented it.

Three Things R8 Does

TaskWhat it means for TrailLog
ShrinkingRemoves classes, methods, fields, and resources nothing actually reaches at runtime — an unused library feature TrailLog never calls simply disappears from the final APK, cutting its size.
ObfuscationRenames surviving classes and methods to short, meaningless names (TrailRepository might become a.b.c) — it doesn't stop determined reverse-engineering, but it removes the easy, readable roadmap an attacker would otherwise get for free.
OptimizationRewrites and restructures bytecode — inlining short methods, removing dead branches — for a measurably faster, leaner runtime footprint, with no visible behavior change.

Turning R8 On for a Release Build

R8 is off by default for debug builds (you want readable stack traces while developing) and is enabled per build type in build.gradle.kts:

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

isMinifyEnabled = true is the switch that turns R8 on for that build type. isShrinkResources = true goes a step further and removes unused resources too — layout files, drawables, strings nothing references — but it only has an effect once minification is also on, since it relies on R8's analysis of what code (and therefore what resources) is actually reachable. proguardFiles(...) lists the rule files R8 reads: Android's own sensible defaults, plus TrailLog's own proguard-rules.pro.

Keep Rules: Protecting Code R8 Can't Safely Guess About

R8's shrinking and renaming both rely on statically tracing which code is reachable from your app's entry points. That works perfectly for ordinary method calls — but it breaks down for code reached through reflection, where a class or property name is looked up as a string at runtime rather than referenced directly in code R8 can see. kotlinx.serialization, used back in JSON & kotlinx.serialization to turn a Trail into JSON and back, does exactly this: it needs the real, un-renamed property names of an @Serializable class at runtime to match them against JSON keys. If R8 obfuscates Trail's property names without knowing that, serialization silently breaks in a release build while working fine in debug.

The fix is a keep rule in proguard-rules.pro, telling R8 explicitly: leave this class, and everything about it, exactly as written.

# proguard-rules.pro
# Keep TrailLog's serializable data classes untouched — kotlinx.serialization
# reads their real property names at runtime through reflection.
-keep @kotlinx.serialization.Serializable class com.codingwithsonu.traillog.model.** { *; }

# Keep the generated serializer companions kotlinx.serialization relies on
-keepclassmembers class com.codingwithsonu.traillog.model.** {
    *** Companion;
}
-keepclasseswithmembers class com.codingwithsonu.traillog.model.** {
    kotlinx.serialization.KSerializer serializer(...);
}

The -keep rule with { *; } tells R8 to leave every member of every matching class untouched — no renaming, no removal — which is exactly what a reflection-driven library needs from its data classes. Rules like this exist specifically for the gap between what R8 can prove is safe by static analysis and what your app actually needs at runtime through reflection, annotations, or JNI.

Note: This build.gradle.kts configuration and proguard-rules.pro syntax are correct, real Android build configuration — but this website's own project doesn't run a live Android Studio build, so nothing here executes on the page itself.

Test a Release Build Locally Before You Publish

This is the single most important habit in this lesson: build and manually test a minified release build on a real device or emulator before ever uploading it anywhere. R8-related bugs — a crash from a class that got stripped, a feature that silently breaks because reflection couldn't find a renamed method — routinely don't show up in debug builds at all, since R8 doesn't run there. The failure mode is specifically a release-build problem, so it can only be caught by actually running a release build yourself first, rather than trusting that "it worked in debug" means anything about how it'll behave once shrunk and obfuscated.

Most Libraries Already Ship Their Own Rules

You won't need to hand-write keep rules for every library TrailLog depends on. Most well-maintained libraries — Retrofit, Room, Gson, and others — bundle their own default R8/ProGuard rules inside their .aar package, which R8 automatically picks up and applies. Custom keep rules are mainly needed for your own reflection-dependent code, like TrailLog's serializable models above, not for well-behaved third-party dependencies.

Up next: With the release build itself locked down, the next few lessons turn to what TrailLog actually shows the user while the app runs. Notifications & Channels covers how to reach the user even when TrailLog isn't in the foreground.