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