So i'm quite new to Dagger and trying to "daggerify" Kotlin MVP project with activities plus independent fragments plus independent navigation.
The fact that i'm using a good few views based on support lib's Fragment made me try recent dagger android support features. After some struggling with compononent graph building i've faced the issue which is represented by this error:
e: ...\MyApp\app\build\tmp\kapt3\stubs\debug\com\...\di\app\MyAppComponent.java:6: error: [dagger.android.AndroidInjector.inject(T)] android.support.v4.app.FragmentManager cannot be provided without an @Provides- or @Produces-annotated method.
e:
e: public abstract interface MyAppComponent extends dagger.android.AndroidInjector<myapp.ui.MyApp> {
e: ^
e: android.support.v4.app.FragmentManager is injected at
e: myapp.ui.common.BaseActivity.fragmentManager
e: myapp.ui.main.MainActivity is injected at
e: dagger.android.AndroidInjector.inject(arg0)
e: java.lang.IllegalStateException: failed to analyze: org.jetbrains.kotlin.kapt3.diagnostic.KaptError: Error while annotation processing
at org.jetbrains.kotlin.analyzer.AnalysisResult.throwIfError(AnalysisResult.kt:57)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules(KotlinToJVMBytecodeCompiler.kt:137)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:158)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:61)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:107)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.java:51)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$1$2.invoke(CompileServiceImpl.kt:386)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$compile$1$2.invoke(CompileServiceImpl.kt:96)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:892)
at org.jetbrains.kotlin.daemon.CompileServiceImpl$doCompile$$inlined$ifAlive$lambda$2.invoke(CompileServiceImpl.kt:96)
at org.jetbrains.kotlin.daemon.common.DummyProfiler.withMeasure(PerfUtils.kt:137)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.checkedCompile(CompileServiceImpl.kt:919)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.doCompile(CompileServiceImpl.kt:891)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:385)
at sun.reflect.GeneratedMethodAccessor95.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:346)
at sun.rmi.transport.Transport$1.run(Transport.java:200)
at sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.jetbrains.kotlin.kapt3.diagnostic.KaptError: Error while annotation processing
at org.jetbrains.kotlin.kapt3.AnnotationProcessingKt.doAnnotationProcessing(annotationProcessing.kt:90)
at org.jetbrains.kotlin.kapt3.AnnotationProcessingKt.doAnnotationProcessing$default(annotationProcessing.kt:42)
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.runAnnotationProcessing(Kapt3Extension.kt:205)
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:166)
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:82)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM$analyzeFilesWithJavaIntegration$2.invoke(TopDownAnalyzerFacadeForJVM.kt:96)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:106)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:83)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:376)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:67)
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:96)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:367)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules(KotlinToJVMBytecodeCompiler.kt:132)
... 29 more
FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:kaptDebugKotlin'.
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
])
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
}
}
}
BaseFragmentModule:
@Module
class BaseFragmentModule {
@Module
companion object {
const val FRAGMENT = "BaseFragmentModule.fragment"
const val CHILD_FRAGMENT_MANAGER = "BaseFragmentModule.childFragmentManager"
@JvmStatic
@Provides
@Named(CHILD_FRAGMENT_MANAGER)
@PerFragment
fun childFragmentManager(@Named(FRAGMENT) fragment: Fragment): FragmentManager {
return fragment.childFragmentManager
}
}
}
BaseChildFragmentModule:
@Module
class BaseChildFragmentModule {
companion object {
const val CHILD_FRAGMENT = "BaseChildFragmentModule.childFragment"
}
}
Sample MainActivityModule:
@Module(includes = [
BaseActivityModule::class
])
abstract class MainActivityModule {
@Binds
@PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
@PerFragment
@ContributesAndroidInjector(modules = [LocationFragmentModule::class])
abstract fun locationFragmentInjector(): LocationFragment
//... other related fragments injection methods
}
I treat fragments as independent views, so each fragment has 1 View Module and 1 Presenter Module ATM. Here is the sample of fragment's DI parts:
@Module(includes = [BaseFragmentModule::class, LocationPresenterModule::class])
abstract class LocationFragmentModule {
@Binds
@Named(BaseFragmentModule.FRAGMENT)
@PerFragment
abstract fun fragment(locationFragment: LocationFragment): Fragment
@Binds
@PerFragment
abstract fun locationView(locationFragment: LocationFragment): LocationView
}
@Module
abstract class LocationPresenterModule {
@Binds
@PerFragment
abstract fun locationPresenter(locationPresenterImpl: LocationPresenterImpl): LocationPresenter
}
Gradle dagger dependencies:
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
implementation "com.google.dagger:dagger-android:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-support:$dagger_version"
I've tried to move AndroidSupportInjectionModule::class one level higher, to main component's modules array.
I hope it's framework-related code generation issue and I just have a bad understanding of support mechanism behaviour, because all module's parts where @Provides is supposed to be already contain this annotation.
As it can be seen from MyApp class code snippet, currently i'm stuck to extend MultiDexApplication, so i wonder could this issue be related to MultiDex supporting.
The situation looks strange to me, so I'd like to be pointed in the right direction by more experienced dagger users.
EDIT:
Here is BaseActivity code:
abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
@JvmField
var navigationManager: NavigationManager? = null
@Inject
@JvmField
var locationManager: MyLocationManagerImpl? = null
@Inject
@JvmField
@Named(BaseActivityModule.ACTIVITY_FRAGMENT_MANAGER)
//Tried to replace with option below
//@field:Named(BaseActivityModule.ACTIVITY_FRAGMENT_MANAGER)
var fragmentManager: FragmentManager? = null
@Inject
@JvmField
var fragmentInjector: DispatchingAndroidInjector<Fragment>? = null
override fun onCreate(@Nullable savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
return fragmentInjector
}
}
BaseFragment:
abstract class BaseFragment : Fragment(), HasSupportFragmentInjector {
@Inject
@JvmField
var activityContext: Context? = null
@Inject
@JvmField
var parentActivity: FragmentActivity? = null
@Inject
@JvmField
var fragmentListener: FragmentListener? = null
@Inject
@JvmField
@Named(BaseFragmentModule.CHILD_FRAGMENT_MANAGER)
//Also tried option below
//@field:Named(BaseFragmentModule.CHILD_FRAGMENT_MANAGER)
var ownChildFragmentManager: FragmentManager? = null
@Inject
@JvmField
var childFragmentInjector: DispatchingAndroidInjector<Fragment>? = null
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
if (context is FragmentListener) {
parentActivity = context as FragmentActivity
fragmentListener = context
}
super.onAttach(context)
}
@Suppress("DEPRECATION")
override fun onAttach(activity: Activity) {
AndroidSupportInjection.inject(this)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if(activity is FragmentListener) {
parentActivity = activity as FragmentActivity
fragmentListener = activity
}
}
super.onAttach(activity)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
return childFragmentInjector
}
fun addChildFragment(@IdRes containerViewId: Int, fragment: Fragment) {
childFragmentManager!!.beginTransaction()
.add(containerViewId, fragment)
.commit()
}
}
Also, full error code was added to the top.
Ok, in this separate case the reason was in non-proper assignments of Fragment
objects in activity and operating them as own properties - more specifically, inside MainActivity
, which extend the base class with filed injection of topical type.
So yes, dagger error log gave indirect hint where to dig.