sockeqwe / mosby

A Model-View-Presenter / Model-View-Intent library for modern Android apps
http://hannesdorfmann.com/mosby/
Apache License 2.0
5.49k stars 841 forks source link

MvpNullObjectBasePresenter + R8 = ClassCastException #334

Closed italankin closed 4 years ago

italankin commented 4 years ago

We are using MvpNullObjectBasePresenter as a base class for our presenters, and discovered that enabling R8 code shrinker can cause crashes.

The reason is R8 has an optimization that removes interfaces with only one implementation.

For example, if you have a SomeMvpView which is implemented only by SomeMvpFragment, R8 will remove SomeMvpView interface and make all casts and calls to SomeMvpFragment.

Having byte code like this:

check-cast v0, Lcom/example/SomeMvpView;
invoke-interface {v0}, Lcom/example/SomeMvpView;->doSomeStuff()V

With R8 optimization it becomes:

check-cast v0, Lcom/example/SomeMvpFragment;
invoke-virtual {v0}, Lcom/example/SomeMvpFragment;->doSomeStuff()V

So it will crash, because java.lang.reflect.Proxy cannot be cast to SomeMvpFragment.

As a solution, we added this line to R8 config to make it work:

-keep interface * extends com.hannesdorfmann.mosby3.mvp.MvpView

Since mvp-nullobject-presenter is a Java library, it cannot use consumerProguardFiles, so I think you should consider adding to documentation this (or similar) rule.

Mosby Version: 3.1.1

Expected behavior The view interface should not be removed, the program should call an interface method.

Actual behavior (include a stacktrace if crash)

ava.lang.ClassCastException: $Proxy1 cannot be cast to com.example.nop.sample.SampleFragment
        at com.example.nop.sample.SamplePresenter.onSomeEvent(SamplePresenter.kt:10)
        at com.example.nop.sample.SampleFragment.onStart(SampleFragment.kt:14)
        at androidx.fragment.app.Fragment.performStart(Fragment.java:2477)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1494)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3269)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:3235)
        at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:212)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:628)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:178)
        at com.hannesdorfmann.mosby3.mvp.MvpActivity.onStart(MvpActivity.java:66)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1391)
        at android.app.Activity.performStart(Activity.java:7157)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2937)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Steps to reproduce the behavior or link to a sample repository Sample app: https://github.com/italankin/mosby-nop-crash (just run and click "Crash" button)

ekursakov commented 4 years ago

R8 actually can consume proguard rules from Java libraries. You only need to put rules to resources/META-INF/proguard/*.pro. For example: https://github.com/square/okhttp/blob/a16ec15/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro

sockeqwe commented 4 years ago

Do you want to sent a PR to add this rules? Happy to include it in next release.

wqbs369 commented 10 months ago

very good solve my problem.