Missing Android consumer proguard rules
See original GitHub issueDescribe the bug
We started using shrinking/obfuscation for our java code by setting minifyEnabled true
in our build.gradle
. We build release builds for testing with the command shown here via ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release
. In your docs you mention adding your proguard file shown here, but that doesn’t seem to be enough to test on minified builds, and either our the app will crash in tests or Detox will log errors and tests will fail.
First, we were experiencing immediate crashes when running tests from Exceptions relating to missing classes. There is an existing issue with Android Instrumentation testing in general when minified, so Slack made a plugin called Keeper to address this for now. The short explanation is that Detox relies on some dependencies in the main app apk like Kotlin and React-Native that the test apk is then not able to find at runtime. They are being removed from the main apk during the shrinking process because the main app is not using those classes. This plugin analyzes test code and adds Proguard rules for the main app for classes the test apk relies on. That’s a separate issue, but just noting as it’s not mentioned in your docs as potential watch-out.
That fixed some crashes, but your library also uses reflection in a number of your classes, and the compiler cannot know what classes you’re trying to use dynamically, so they could get removed or obfuscated. We then need additional proguard rules that don’t seem to be in your proguard-rules-app.pro
file. For example, we were using react-native 0.63.2
and Detox 17.6.1
, and we see several errors like this when running tests on minified builds:
E Detox : Can't set up RN UIModule listener
E Detox : org.joor.ReflectException: java.lang.NoSuchFieldException: mOperationsQueue
E Detox : at org.joor.Reflect.field0(Reflect.java:310)
E Detox : at org.joor.Reflect.field(Reflect.java:282)
E Detox : at com.wix.detox.reactnative.idlingresources.UIModuleIdlingResource.isIdleNow(UIModuleIdlingResource.java:84)
E Detox : at androidx.test.espresso.base.IdlingResourceRegistry.allResourcesAreIdle(IdlingResourceRegistry.java:268)
E Detox : at androidx.test.espresso.base.IdlingResourceRegistry$6.isIdleNow(IdlingResourceRegistry.java:302)
E Detox : at androidx.test.espresso.base.UiControllerImpl.loopMainThreadUntilIdle(UiControllerImpl.java:453)
E Detox : at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:275)
E Detox : at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:272)
E Detox : at java.util.concurrent.FutureTask.run(FutureTask.java:266)
E Detox : at android.os.Handler.handleCallback(Handler.java:790)
E Detox : at android.os.Handler.dispatchMessage(Handler.java:99)
E Detox : at android.os.Looper.loop(Looper.java:164)
E Detox : at android.app.ActivityThread.main(ActivityThread.java:6494)
E Detox : at java.lang.reflect.Method.invoke(Native Method)
E Detox : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
E Detox : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
E Detox : Caused by: java.lang.NoSuchFieldException: mOperationsQueue
E Detox : at java.lang.Class.getField(Class.java:1601)
E Detox : at org.joor.Reflect.field0(Reflect.java:295)
E Detox : ... 15 more
I was able to fix some of these just by adding Proguard rules to our main app to just keep all of certain packages as I didn’t know all the classes you might use reflection on.
-keep class com.facebook.react.modules.** { *; }
-keep class com.facebook.react.animated.** { *; }
-keep class com.facebook.react.uimanager.** { *; }
However, today we tried upgrading to 17.10.3
as there are new features and that led to a new crash:
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: to.hover.android.app, PID: 26450
org.joor.ReflectException: org.joor.ReflectException: java.lang.NoSuchFieldException: mTasks
at org.joor.Reflect.field(Reflect.java:286)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.pendingTasks(SerialExecutorReflected.kt:14)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.hasPendingTasks(SerialExecutorReflected.kt:9)
at com.wix.detox.reactnative.idlingresources.AsyncStorageIdlingResource$idleCheckTaskImpl$1.run(AsyncStorageIdlingResource.kt:39)
at com.reactnativecommunity.asyncstorage.AsyncStorageModule$g$a.run(AsyncStorageModule.java:1)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: org.joor.ReflectException: java.lang.NoSuchFieldException: mTasks
at org.joor.Reflect.field0(Reflect.java:310)
at org.joor.Reflect.field(Reflect.java:282)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.pendingTasks(SerialExecutorReflected.kt:14)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.hasPendingTasks(SerialExecutorReflected.kt:9)
at com.wix.detox.reactnative.idlingresources.AsyncStorageIdlingResource$idleCheckTaskImpl$1.run(AsyncStorageIdlingResource.kt:39)
at com.reactnativecommunity.asyncstorage.AsyncStorageModule$g$a.run(AsyncStorageModule.java:1)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.NoSuchFieldException: mTasks
at java.lang.Class.getField(Class.java:1601)
at org.joor.Reflect.field0(Reflect.java:295)
at org.joor.Reflect.field(Reflect.java:282)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.pendingTasks(SerialExecutorReflected.kt:14)
at com.wix.detox.reactnative.idlingresources.SerialExecutorReflected.hasPendingTasks(SerialExecutorReflected.kt:9)
at com.wix.detox.reactnative.idlingresources.AsyncStorageIdlingResource$idleCheckTaskImpl$1.run(AsyncStorageIdlingResource.kt:39)
at com.reactnativecommunity.asyncstorage.AsyncStorageModule$g$a.run(AsyncStorageModule.java:1)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
It appears that another class AsyncStorageIdlingResource
was added that uses reflection on the com.reactnativecommunity.asyncstorage.AsyncStorageModule
class from another library we use.
My ask then is can you provide the necessary proguard rules for any case in which you use reflection? It’s important for us to be able to test on minified builds, and it’s difficult and unsustainable for us to look through your code and figure our these rules ourselves and things could still break when you add something new with reflection in between upgrades, like happened with us. These rules can just be included in your proguard-rules-app.pro
file or at least suggestions for rules in your docs. Thank you.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:5
@d4vidi .
So this is turning out to be quite an eye opener – indeed, we were not properly compliant with obfuscating apps! I will have that worked out asap. Thank you!