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

走远了吗. 提交于 2019-12-22 08:14:22

问题


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.

来源:https://stackoverflow.com/questions/47943831/kotlin-dagger-inject-issue-depending-on-devices-android-version-sdk

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