Search code examples
androidkotlindagger-2dagger

android.support.v4.app.FragmentManager cannot be provided without an @Provides- or @Produces-annotated method


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.


Solution

  • 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.