Kotlin + Dagger inject issue depending on device's Android Version / SDK (?)

匿名 (未验证) 提交于 2019-12-03 00:53:01

问题:

Last week, while implementing Dagger in my current Kotlin MVP project, I was testing it on oldy phone with KitKat 4.4.2 (yep, it still supports all major material features and stuff :)) due to primary phone's maintenance. So that week I was having typical issues rather than something unusual and fixed them more or less quickly by investigating provided errors. At long last, code compiled, current project version was built without issues and ran without major bugs on KitKat with UI interacting.

But when I took main phone with Nougat 7.1.2 from repair center and launched app on it I stuck with weird DI-related issue. Right after that I also launched app on mate's Marshmallow 6.0 and caught one more, exactly the same. The issue is briefly described this way:

  • App (successfully) launches;

  • I'm able to operate ViewPager/DrawerLayout/etc ui features which are provided through context and fragmentManager injection;

  • All services are also available and running through injection as expected;

  • App crashes when i'm twitching Activity's Presenter.

And now the funniest part, which drives me crazy: All accessible classes, which don't bring any problems, are injected through constructor injection.

But the Presenter instance, which is injected using field injection, is not initialized when needed.

Surely, I tried not to use lateinit modifier and to inject it like nullable field with @JvmField or without it: result is the same - it's not injected at all.

As the issue is related to Activity, i have the "natural constraint" not to use primary constructor for injection.

Here is the exception, which is not very informative to me, except the first string:

kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized     at .ui.common.view.BaseViewActivity.getPresenter(BaseViewActivity.kt:14)     at .ui.main.view.MainActivity.onPlaceTypeClick(MainActivity.kt:143)     at .ui.types.nearby.view.NearbyPlaceTypeItemViewHolder$bind$1.onClick(NearbyPlaceTypeItemViewHolder.kt:32)     at android.view.View.performClick(View.java:5647)     at android.view.View$PerformClick.run(View.java:22462)     at android.os.Handler.handleCallback(Handler.java:754)     at android.os.Handler.dispatchMessage(Handler.java:95)     at android.os.Looper.loop(Looper.java:163)     at android.app.ActivityThread.main(ActivityThread.java:6205)     at java.lang.reflect.Method.invoke(Native Method)     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794) 

Here is some code:

App:

class MyApp : MultiDexApplication(), HasActivityInjector {      @Inject     @JvmField     var activityInjector: DispatchingAndroidInjector<Activity>? = null      override fun onCreate() {         super.onCreate()         DaggerMyAppComponent.builder().create(this).inject(this)     }      override fun activityInjector(): AndroidInjector<Activity>? {         return activityInjector     } } 

AppComponent:

@Singleton @Component(modules = [     MyAppModule::class,     DataModule::class,     PreferencesModule::class,     ServiceModule::class,     NavigationModule::class ]) interface MyAppComponent : AndroidInjector<MyApp> {      @Component.Builder     abstract class Builder : AndroidInjector.Builder<MyApp>() } 

AppModule:

@Module(includes = [AndroidSupportInjectionModule::class]) abstract class MyAppModule {      @Binds     @Singleton     abstract fun application(myApp: MyApp): Application      @PerActivity     @ContributesAndroidInjector(modules = [(MainActivityModule::class)])     abstract fun mainActivityInjector(): MainActivity      //... other activity injectors } 

BaseActivityModule:

@Module abstract class BaseActivityModule {      @Binds     @PerActivity     internal abstract fun activity(appCompatActivity: AppCompatActivity): Activity      @Binds     @PerActivity     internal abstract fun activityContext(activity: Activity): Context      @Module     companion object {          const val ACTIVITY_FRAGMENT_MANAGER = "BaseActivityModule.activityFragmentManager"          @JvmStatic         @Provides         @Named(ACTIVITY_FRAGMENT_MANAGER)         @PerActivity         fun activityFragmentManager(activity: AppCompatActivity): FragmentManager {             return activity.supportFragmentManager         }     } } 

At the moment i have kinda wide-branching MVP model, so each activity/fragment has 1 View Module and 1 Presenter Module, and all common injections interact through the base classes. Here is the sample of Activity DI parts:

@Module(includes = [     BaseActivityModule::class,     MainPresenterModule::class ]) abstract class MainActivityModule {      @Binds     @PerActivity     abstract fun mainView(mainActivity: MainActivity): MainView      @Binds     @PerActivity     abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity      @PerFragment     @ContributesAndroidInjector(modules = [LocationFragmentModule::class])     abstract fun locationFragmentInjector(): LocationFragment      //... other related fragments injection methods }   @Module abstract class MainPresenterModule {      @Binds     @PerActivity     abstract fun mainPresenter(mainPresenterImpl: MainPresenterImpl): MainPresenter } 

BaseActivity:

abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {      @Inject     @field:Named(BaseActivityModule.ACTIVITY_FRAGMENT_MANAGER)     lateinit var fragmentManager: FragmentManager      @Inject     lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>      override fun onCreate(@Nullable savedInstanceState: Bundle?) {         AndroidInjection.inject(this)         super.onCreate(savedInstanceState)     }      override fun supportFragmentInjector(): AndroidInjector<Fragment>? {         return fragmentInjector     } } 

Example of using constructor injection - MainPresenter (all injections work - no problems here):

@PerActivity class MainPresenterImpl @Inject constructor(         val navigationManager: NavigationManager,         val locationManager: LocationManagerImpl,         val sharedPrefsRepo: SharedPreferencesRepository,         view: MainActivity) : BaseViewPresenter<MainView>(view), MainPresenter { } 

And below is the place where all "magic" begins - var presenter is not initializing on any circumstances.

abstract class BaseViewActivity<T : MVPresenter> (): BaseActivity(), MVPView {      @Inject     lateinit var presenter: T } 

BUT, as said, that's NOT happening on KitKat - app is running on it without any problems. So, once more: I presume that problem exists when app is running on Marshmallow+ (unfortunately, don't know about Lollipop yet).

I really wonder if someone more experinced had issue like this before. No build errors, no dagger exceptions at all; just non-initialized property.

Could it be related to permissioning in some way? The only danger permission that i have is device's location acess, and it've beed held and tested.

After all, if there is a posibility of supporting/versioning issue, what other aspect could it been related to?

Thanks in advance.

UPD:

Seems that presenter instance is bound to Activity right after its onCreate(), but un-binds when some callback action with Activity in role of listener is triggered. So, it looks like that Kotlin looks up to presenter again, but finds it uninitialized. Still don't get why this happens only on newer platforms.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!