Skip to main content

Android - Consumer ProGuard Rules

When creating a module library for the Q2 Mobile SDK, you need to provide ProGuard/R8 rules that ensure your library works correctly when code shrinking and obfuscation are enabled. This guide explains what consumer rules are, why they're needed, and how to create and test them.

What is R8 and Code Shrinking?

R8 is Android's code shrinker and obfuscator. When building release versions of Android apps, R8:

  • Shrinks code by removing unused classes and methods
  • Obfuscates code by renaming classes, methods, and fields to shorter names
  • Optimizes code to improve performance

The Q2 Mobile App enables R8 during release builds, so all consumed libraries must provide proper rules to ensure they work correctly in production.

For more details, see:

Why Your Module Needs Consumer Rules

Without proper ProGuard/R8 rules, your module may encounter runtime errors in release builds:

  • ClassNotFoundException - Classes your module needs get removed
  • NoSuchMethodException - Methods called via reflection are renamed or removed
  • Serialization failures - Data classes used for JSON/XML parsing break
  • Crashes - Code that works in debug builds fails in production

Consumer rules protect your library's public API and critical internal code from being modified or removed.

Keep-All Rules Will Be Rejected

Do NOT use class-level wildcard rules:

  • -keep class ** { *; }
  • -keep class com.yourcompany.** { *; }

Rules that keep entire packages or namespaces will be rejected during code review. These rules:

  • Defeat the purpose of R8 optimization
  • Significantly increase app size
  • Reduce performance
  • Create security concerns

Be specific: Only keep the exact classes your module needs to function. You must list each class individually:

Acceptable: -keep class com.yourcompany.MySpecificClass { *; } (specific class, all members)

Will be rejected: -keep class com.yourcompany.** { *; } (all classes in package)

Q2 requires secure, targeted rules that protect only what's necessary.

ProGuard Rules vs Consumer Rules

Your module can have two types of ProGuard files:

proguard-rules.pro

  • Purpose: Rules that apply when building your module in the DevApp
  • Scope: Only affects your module during development
  • Not included: These rules are NOT passed to consuming applications
  • Use for: Testing and debugging your module locally

consumer-rules.pro

  • Purpose: Rules that your module exports to consuming applications
  • Scope: Applied when the Q2 Mobile App (or any app) includes your module
  • Automatically included: The Q2 Mobile App automatically uses these rules
  • Use for: Protecting your module's API and functionality in production

Important: Always put rules in consumer-rules.pro if they're needed for your module to work in production builds.

Basic Rule Examples

Here are common rule patterns you'll need for your module. Remember: be specific - keep only what's necessary, not entire packages.

When Wildcards are Acceptable

Member-level wildcards { *; } on a specific class are acceptable and often necessary:

Acceptable: -keep class com.yourcompany.MyModule { *; } - Keeps all members of one specific class

Rejected: -keep class com.yourcompany.** { *; } - Keeps all classes in the package

Special cases where class wildcards are acceptable:

  • -keepclassmembers rules (only affect already-kept classes)
  • -keepclasseswithmembernames rules (conditional, standard Android patterns)
  • Annotation-based rules (very specific about what they keep)

Never acceptable:

  • -keep class ** or -keep class com.yourcompany.** (keeps entire package hierarchies)

Keep Your Public API

Keep specific classes and methods that are called by the Q2 Mobile SDK or from JavaScript:

# Keep your module entry points - interfaces/base classes need all members preserved
-keep class com.yourcompany.yourmodule.MyMethodModule { *; }
-keep class com.yourcompany.yourmodule.MyUIModule { *; }

# Keep specific public classes (list each one explicitly)
-keep class com.yourcompany.yourmodule.PaymentHandler { *; }
-keep class com.yourcompany.yourmodule.AuthenticationManager {
public <methods>;
}

# If you only need specific methods, be even more specific
-keep class com.yourcompany.yourmodule.UtilityClass {
public void initialize();
public java.lang.String getVersion();
}

Keep Classes Used for Serialization

If your module uses Gson, Moshi, or other serialization libraries, keep your data classes. Data models typically need all members preserved:

# Keep specific data model classes (list each one)
-keep class com.yourcompany.yourmodule.models.UserData { *; }
-keep class com.yourcompany.yourmodule.models.TransactionResponse { *; }
-keep class com.yourcompany.yourmodule.models.AccountInfo { *; }

# For Gson specifically, also keep field names
# Note: -keepclassmembers only affects classes already kept above, so package
# wildcards are acceptable here (it won't keep new classes, just their fields)
-keepclassmembers class com.yourcompany.yourmodule.models.** {
<fields>;
}

# Never use: -keep class com.yourcompany.yourmodule.models.** { *; }
# This would keep ALL classes in the package - list them individually instead

Keep Code That Uses Reflection

If your module uses reflection or is called via reflection:

# Keep specific classes accessed via reflection
-keep class com.yourcompany.yourmodule.ReflectionHelper { *; }
-keep class com.yourcompany.yourmodule.DynamicLoader { *; }

# Keep methods with specific annotations (this is acceptable use of class wildcard)
-keepclassmembers class * {
@com.yourcompany.yourmodule.annotations.KeepMethod *;
}

# Keep specific constructors used by reflection
-keepclassmembers class com.yourcompany.yourmodule.PluginManager {
public <init>(...);
}

Keep Native Methods

Only add this rule if your module actually uses native code (JNI/NDK).

If your module includes .so files or uses Java methods declared with the native keyword, you need to protect those method signatures:

# Keep native method declarations
# Note: This rule uses 'class *' but is conditional - it ONLY keeps classes
# that contain native methods, not all classes in your app
-keepclasseswithmembernames class * {
native <methods>;
}

Why this wildcard is acceptable: The -keepclasseswithmembernames directive only applies to classes that match the condition (having native methods). It won't keep your entire module or unrelated classes.

When to use: Only if your module:

  • Has a jniLibs folder with .so files
  • Declares methods with the native keyword in Java/Kotlin
  • Uses NDK for C/C++ integration

Most modules don't need this rule - only add it if you're certain you're using native code. Don't copy-paste it "just in case."

Where to Add Consumer Rules

Your module should already have consumer rules configured by default. Check your module's build.gradle file - you should see this in the release buildType:

buildTypes {
release {
// minifyEnabled should remain false, as your module will be released as a library.
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'

// Before releasing, add your rules to the consumerProguardFiles.
consumerProguardFiles 'consumer-rules.pro'
}
}

The consumer-rules.pro file should already exist in your module's root directory (same location as build.gradle). If it's missing, create it.

You don't need to modify the release buildType configuration - just add your rules to the consumer-rules.pro file.

Testing Consumer Rules in Debug Builds (Optional)

If you want to test your consumer rules locally before certification builds, you can add the same configuration to your debug buildType:

buildTypes {
debug {
minifyEnabled true // Enable for testing
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
consumerProguardFiles 'consumer-rules.pro'
}
release {
// ... keep existing release configuration
}
}

This allows you to catch issues with your consumer rules early during local development.

How to Test Your Consumer Rules

Testing is critical to ensure your module works correctly with R8 enabled.

1. Enable Minify in DevApp

In your DevApp's build.gradle, enable minify for debug builds:

buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}

2. Build and Test

Build the DevApp and test all your module's functionality:

./gradlew assembleDebug

Install and thoroughly test:

  • All module methods work correctly
  • No crashes when calling your module from JavaScript
  • Data serialization/deserialization works
  • UI modules display correctly

3. Check for Common Issues

Watch for these error patterns in Logcat:

  • ClassNotFoundException - Add -keep rule for the missing class
  • NoSuchMethodException - Keep the specific method being called
  • JSON parsing errors - Keep your data model classes
  • Reflection errors - Keep classes/methods accessed via reflection

Troubleshooting

My module works in debug but crashes in release

Solution: You're missing consumer rules. Check the crash logs for the class or method name, then add a -keep rule for it.

How do I know what rules I need?

Solution:

  1. Look at your module's public API - these classes/methods need -keep rules
  2. Check for classes used in JSON/XML serialization - keep data models
  3. Look for reflection usage - keep reflected classes
  4. Enable minify in DevApp and test - errors will tell you what's missing

Should I keep everything with -keep class ** { *; }?

Solution: Absolutely not. Keep-all rules will be rejected during Q2's code review process. Over-keeping defeats the purpose of R8, increases app size, reduces performance, and creates security concerns. Only keep what's necessary for your module to function. Be specific about what classes and methods need protection. If you're unsure what to keep, enable minify in DevApp and test - the errors will guide you to the specific classes that need rules.

R8 is removing code my module needs internally

Solution: If R8 removes internal code that's actually used, it means R8 can't detect the usage (often due to reflection or indirect calls). Add specific -keep rules for those classes/methods.

Best Practices

  1. Start minimal - Only add rules as you discover they're needed
  2. Be specific - Use full package names and class names (never use keep-all wildcards)
  3. No keep-all rules - Rules like -keep class ** { *; } or package-wide keeps will be rejected
  4. Test with minify enabled - Don't wait until Certification builds to discover issues
  5. Document your rules - Add comments explaining why each rule is needed
  6. Keep data models - Always keep classes used for serialization/deserialization
  7. Update rules with changes - When you add new public APIs, update your rules

Additional Resources


If you encounter issues with ProGuard rules during Certification builds, ensure your consumer-rules.pro file is properly configured and tested with minify enabled locally first. For additional assistance, contact Q2 support.