Mastering ProGuard in Android Multi-Module Projects: AGP 8.4+, R8, and Consumable Rules
1. Introduction + What is ProGuard?
When building Android apps, especially at scale, securing your code and reducing APK and bundle size becomes essential. This is where tools like ProGuard (and its modern replacement, R8) come into play.
ProGuard performs:
- Shrinking: Removes unused classes and methods
- Obfuscation: Renames classes, methods, and fields to meaningless names
- Optimisation: Improves byte-code performance
⚠️ R8 vs ProGuard: R8 has replaced ProGuard as the default tool in Android Gradle Plugin (AGP) 3.4+. While R8 handles shrinking and obfuscation, it still uses ProGuard-compatible rules. You don’t need to install ProGuard separately.
This makes understanding and writing clean ProGuard rules more critical than ever, especially for teams dealing with multi-module architectures.
2. What Changed in AGP 8.4+ for ProGuard?
Android Gradle Plugin (AGP) 8.4 brought an important restriction: minifyEnabled true
is now disallowed in library modules.
https://developer.android.com/build/releases/past-releases/agp-8-4-0-release-notes
Why? Because AGP has shifted the responsibility of code shrinking and obfuscation solely to the application module. The library modules should no longer perform minification on their own.
- What to use instead?
consumerProguardFiles(...)
Old (invalid in AGP 8.4+):
// ❌ This will fail
buildTypes {
release {
minifyEnabled true
}
}
New (valid):
android {
defaultConfig {
consumerProguardFiles(
"proguard-rules.pro",
"retrofit-proguard-rules.pro"
)
}
}
The rules you define in consumer-rules.pro
or consumerProguardFiles(...)
get merged into the application’s ProGuard rules during build time.
This change also aligns with how libraries are expected to behave: they shouldn’t make assumptions about shrinker settings, but they must communicate their requirements to consuming apps.
3. Understanding consumer-rules.pro
This file acts as a contract between a library and its consumers. If a library depends on reflection, uses annotations, or has classes that must not be stripped out, it should declare them here.
Some key use cases include:
- Gson/Retrofit models that rely on annotations.
- Glide modules with generated classes.
- Keeping specific base interfaces or internal frameworks intact.
# Example
# For Retrofit & Gson
-keep class retrofit2.** { *; }
-keep interface retrofit2.Call
-keep class * {
@com.google.gson.annotations.SerializedName <fields>;
}
These rules ensure the consuming app retains required symbols after obfuscation.
4. Multi-Module ProGuard Architecture
If you have 5, 10, or even 20+ modules in a codebase, rule management can become chaotic. That’s where a clear architecture helps:
- App module holds global rules and final
minifyEnabled true
config. - Library modules define only the required keep rules via
consumerProguardFiles(...)
. - Third-party rules are organised and versioned.
Recommended Folder Structure:
app/
└── proguard-rules.pro
└── retrofit-proguard-rules.pro
└── glide-proguard-rules.pro
libraryA/
└── consumer-rules.pro
libraryB/
└── consumer-rules.pro
App’s build.gradle
:
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
"retrofit-proguard-rules.pro",
"glide-proguard-rules.pro"
)
5. Real-World ProGuard Rules
📦 Retrofit + Gson
Retrofit uses annotations and reflection. If you’re using @SerializedName
, Gson’s parsing will break without the right ProGuard config.
-keep class retrofit2.** { *; }
-keep interface retrofit2.Call
-keepattributes Signature, RuntimeVisibleAnnotations
-keep class * {
@com.google.gson.annotations.SerializedName <fields>;
}
🌄 Glide
Glide generates code at compile time using annotations. Those generated classes must be preserved.
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl { *; }
-dontwarn okhttp3.**
6. Verifying Your Rules
To avoid runtime crashes, it’s important to test if your rules are being applied.
Add a dummy rule:
-checkdiscard class com.yourcompany.DummyProguardCheck
Build with info logs:
./gradlew :app:minifyReleaseWithR8 --info
Inspect output files:
mapping.txt
: class name mappingsseeds.txt
: retained classesusage.txt
: stripped out classesconfiguration.txt
: final merged ProGuard config
You can also use tools like APK Analyzer or R8 Visualizer to inspect results visually.
7. Conclusion
ProGuard and R8 are more than just code obfuscators. When used correctly in multi-module Android projects, they can:
- Shrink your app size considerably
- Obfuscate your business logic and deter reverse engineering
- Maintain code safety across module boundaries
AGP 8.4 forces a more structured approach, and embracing consumer-rules.pro
is now a necessity—not an option.
Key Takeaways:
- Minification happens only in the app module.
- Use
consumerProguardFiles
in libraries to define required rules. - Modularize your third-party rules to reduce duplication.
- Regularly inspect merged rules to avoid accidental stripping.
💡 Pro Tip: Keep a test build variant like
minifyDebug
to test obfuscation during development.
Feel free to share this post with your team or peers — it might save someone hours of painful debugging. And if you have your own tips or practices around ProGuard, I’d love to hear them!
Thanks for reading 🙌