configuring-r8-for-compose — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited configuring-r8-for-compose (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
R8 is the only supported shrinker — ProGuard is deprecated. Compose ships consumer ProGuard rules, so most apps need no Compose-specific keep rules. The big modern wins come from R8 full mode (default since AGP 8.0): lambda grouping, sourceInformation() stripping, constant-folding of composable args, and ComposerImpl devirtualization. This skill teaches Claude to set R8 up minimally and refuse over-broad keeps that defeat those wins.
-keep class androidx.compose.** { *; } or any other Compose-wide keep — push back and apply this skill.classes.dex and the developer wants to know why.isMinifyEnabled = true is being flipped on, before any release benchmark is run.../../measurement/testing-compose-in-release-mode/SKILL.md whenever release measurement is being set up.ClassNotFoundException, NoSuchMethodError, or empty reflection results — not behavioral bugs.stability/ or recomposition/ skills.gradle.properties with android.enableR8.fullMode=true.../../measurement/testing-compose-in-release-mode/SKILL.md for measurement signing).org.jetbrains.kotlin.plugin.compose plugin so Compose's bundled consumer rules are wired correctly through dependency resolution.proguard-android.txt (no R8 optimizations) and proguard-android-optimize.txt (R8 optimizations on).Surface these to the developer when they ask "why bother" or push back against minification:
if (changed and 1 == 0) style checks the recomposer can short-circuit.isShrinkResources = true, R8 strips unreachable drawables, strings, and layout XML.Cited measurement: roughly 75 percent startup gain and 60 percent frame-render gain debug to release. Source: Ben Trengrove, "Why should you always test Compose performance in release" (Android Developers Medium).
// app/build.gradle.kts
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
}proguard-android.txt instead of proguard-android-optimize.txt. Only the -optimize variant enables R8's optimization passes.androidx.compose.runtime, androidx.compose.ui, androidx.compose.foundation, androidx.compose.material3, etc.) ships a consumer-rules.pro that R8 picks up automatically. The developer's proguard-rules.pro should contain no lines starting with -keep class androidx.compose..@Serializable data classes consumed by kotlinx.serialization (the kotlinx-serialization Gradle plugin emits the right rules — verify, do not duplicate).rememberSaveable Saver objects only when accessed reflectively from outside the module.Class.forName(...) consumer, JNI binding, or service loader.app/build/outputs/mapping/release/missing_rules.txt listing the exact -keep directives R8 inferred from runtime failures. Add only those, narrowed to the specific class/method.# Modern R8 retrace (cmdline-tools 7.0+):
$ANDROID_HOME/cmdline-tools/latest/bin/retrace \
app/build/outputs/mapping/release/mapping.txt < crash.txtOr use the Gradle convenience target wired into AGP:
./gradlew :app:retraceR8DebuggingArtifact(The legacy ProGuard retrace.sh install path under $ANDROID_HOME/tools/ was removed when SDK Tools 26 was sunset; do not look for it.) Always retrace before diagnosing a release crash — the stack frames are otherwise meaningless.
./gradlew :app:assembleRelease
# Then in Android Studio: Build → Analyze APK → app-release.apk
# Compare classes.dex method count and resources.arsc size against the same build with isMinifyEnabled = false.A correctly configured Compose app with no over-broad keeps typically halves the method count vs the un-minified release.
../../measurement/testing-compose-in-release-mode/SKILL.md. The expected delta vs CompilationMode.None against a debug variant is in the 75 percent / 60 percent range cited above.# WRONG
-keep class androidx.compose.** { *; }
# WRONG because: Compose's own consumer rules are tighter and correct; a wildcard keep blocks lambda grouping, prevents sourceInformation stripping, kills composable-arg constant folding, defeats ComposerImpl devirtualization, and bloats classes.dex. The cited 75/60 gains evaporate.# RIGHT — let Compose's consumer rules handle it.
# (no Compose-specific lines in proguard-rules.pro by default)// WRONG
buildTypes.release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro",
)
}
// WRONG because: proguard-android.txt explicitly skips R8's optimization passes. Lambda grouping, devirtualization, constant folding, sourceInformation stripping all do not run. The release variant builds and installs but performs like a poorly minified app.// RIGHT
buildTypes.release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}kotlin.Metadata keep# WRONG
-keep class kotlin.Metadata { *; }
# WRONG because: blanket-keeping kotlin.Metadata blocks R8 from shrinking metadata for unreachable Kotlin classes; identify the actual reflective consumer (kotlinx.serialization, Moshi, Gson, Hilt) and add a narrow rule for that consumer instead.# RIGHT — narrow to the reflective consumer
# Example: kotlinx.serialization needs Companion access on KSerializer implementations.
-keepclassmembers class * implements kotlinx.serialization.KSerializer {
public static **$Companion Companion;
}-dontobfuscate for "easier debugging"# WRONG
-dontobfuscate
-dontoptimize
# WRONG because: turning off obfuscation and optimization on the release variant removes most of R8's benefit; the variant being measured is no longer the variant that ships. If readable stacks are needed, ship with R8 on and use retrace + the mapping.txt to deobfuscate.# RIGHT — leave R8 on, keep mapping.txt for retrace.
# (no -dontobfuscate / -dontoptimize lines)
# Upload mapping.txt to your crash reporter (Crashlytics, Sentry, etc.) so stacks
# are deobfuscated automatically; or run R8 retrace manually:
# $ANDROID_HOME/cmdline-tools/latest/bin/retrace mapping.txt < crash.txt
# (or: ./gradlew :app:retraceR8DebuggingArtifact)@Serializable keep when the kotlinx-serialization plugin is missing# RIGHT — when using kotlinx.serialization with reflection-based polymorphism
-keepclassmembers class * implements kotlinx.serialization.KSerializer {
public static **$Companion Companion;
}
-keepclasseswithmembers class * {
@kotlinx.serialization.Serializable <init>(...);
}
# But PREFERRED: apply the kotlinx-serialization Gradle plugin so these rules are emitted automatically.rememberSaveable Saver# WRONG — defensive blanket keep
-keep class **.Saver { *; }
# WRONG because: most Saver implementations are reachable from non-reflective code and are kept automatically; a blanket keep prevents inlining and elimination.# RIGHT — only when the Saver is loaded by name reflectively
-keepclassmembers class com.example.feature.MyTypeSaver {
public static *** INSTANCE;
}proguard-android-optimize.txt, not proguard-android.txt. The non-optimize variant disables R8's optimization passes.isMinifyEnabled = true AND isShrinkResources = true for release.-keep class androidx.compose.** { *; }, -keep class * extends androidx.compose.runtime.Composable, etc.). Compose ships correct consumer rules; a blanket keep defeats lambda grouping, sourceInformation stripping, constant folding, and ComposerImpl devirtualization.-dontobfuscate or -dontoptimize to the release variant for "easier debugging". Measure with R8 on and use R8 retrace + mapping.txt for stack readability; debug code paths separately on the debug variant.missing_rules.txt reporter to find narrow under-keeps. Do not pre-emptively keep packages.mapping.txt before drawing conclusions about the cause.kotlinx-serialization, Hilt, Moshi-codegen). The plugins emit the right rules automatically.Build → Analyze APK or a Gradle plugin so a regression in dex size flags an over-broad keep added by a future PR.proguard-rules.pro contains zero lines matching ^-keep .* androidx\.compose\..app/build.gradle.kts release block uses getDefaultProguardFile("proguard-android-optimize.txt") exactly.isMinifyEnabled = true and isShrinkResources = true are both set on the release buildType../gradlew :app:assembleRelease succeeds.classes.dex method count is meaningfully lower than the same release built with isMinifyEnabled = false.CompilationMode.Partial(BaselineProfileMode.Require) against the release variant is faster than the same benchmark with CompilationMode.None.app/build/outputs/mapping/release/mapping.txt.missing_rules.txt was generated, only the listed narrow keeps were added — no broadening.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.