tmurakami/dexopener

UI Tests fail on API 23, but not on API 24+

nealsanche opened this issue · 7 comments

Hello Tsuyoshi,

DexOpener is pretty much the only way we have found to be able to test using Espresso and Mockito along with Kotlin. I have spent some time today trying to figure out why a project I've been working on fails on Android versions lower than 24, but works fine on API 24+. I find that DexOpener is one of the components that seems to make a difference. We were using 0.10.3 of DexOpener, and the behaviour is slightly different with that version, so I decided to upgrade, and then the behaviour changes quite a bit more.

I have been working on getting this to fail on a smaller, non-proprietary project, and have succeeded. I end up getting the following error when run in an API 23 Emulator:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.robotsandpencils.kotlindaggerexperiement/com.robotsandpencils.kotlindaggerexperiement.presentation.main.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property dispatchingActivityInjector has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property dispatchingActivityInjector has not been initialized
at com.robotsandpencils.kotlindaggerexperiement.App.activityInjector(App.kt:52)
at dagger.android.AndroidInjection.inject(AndroidInjection.java:56)
at com.robotsandpencils.kotlindaggerexperiement.presentation.main.MainActivity.onCreate(MainActivity.kt:42)
at android.app.Activity.performCreate(Activity.java:6237)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
at android.support.test.runner.MonitoringInstrumentation.callActivityOnCreate(MonitoringInstrumentation.java:624)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)

But the same code runs fine in API 24 and higher.

The project is on Github: https://github.com/nealsanche/KotlinDagger2Experiment

I wonder if you might have a look and see if there's anything you can suggest. This project seems to also run fine when using the 0.10.3 version of DexOpener (though my larger proprietary project does not).

I wasn't aware of the kotlin-allopen plugin until I read around a bit more on issues that make DexOpener helpful. I tried it on my sample project, and also on my larger project, and it works for me. Still, the mystery of why DexOpener doesn't work is somewhat enticing. Maybe it is lower priority for me now, though.

Thank you for your reporting.

I have not been able to find the cause of the error yet, but I found a workaround for that error.
Instead of calling TestApp::class.java.name on TestRunner#newApplication(), specify a string literal of that class name.

super.newApplication(cl, "com.robotsandpencils.kotlindaggerexperiement.TestApp", context)

I will continue to investigate it.
Thank you!

I investigated this issue.
It seems that the above workaround is the only way to prevent this error.

The following error occurred when testing your project on API 19:

java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
    at com.robotsandpencils.kotlindaggerexperiement.App.resetUserComponent(App.kt:48)
    at com.robotsandpencils.kotlindaggerexperiement.App.onCreate(App.kt:37)

DexOpener creates a DEX file which is a collection of classes to be opened from the original DEX file.
This error occurs when the class to be opened has read from the original DEX file, not from the generated DEX file.

To prevent it, all classes that rely on the classes in your application apk must be loaded via ClassLoader#loadClass().
Therefore, on newApplication(), you will not be able to use the class literal of TestApp which relies on application classes, such as App.

The use of a class literal to replace an Application class is written in README.md, but it is a mistake.
I'm sorry and will fix it later.

Updated README.md.

I have tested using the string literal of the class name as you suggested, and it works on my older emulators and newer ones. With your documentation changes, people will likely avoid the troubles I was having. Thank you, Tsuyoshi