1

MainActivity cannot be provided without an @Inject constructor or an @Provides-annotated method. This type supports members injection but cannot be implicitly provided.

I'm using dagger-android, I injected MainActivity through AndroidInjection.inject(this), but it's still unavailable in Module. I prepared sample project: https://github.com/deepsorrow/test_daggerIssu.git, files listed below:

FactoryVmModule:

@Module
class FactoryVmModule {

    @Provides
    @Named("TestVM")
    fun provideTestVM(
        activity: MainActivity, // <--- dagger can't inject this
        viewModelFactory: ViewModelFactory
    ): TestVM =
        ViewModelProvider(activity, viewModelFactory)[TestVM::class.java]

}

MainActivityModule:

@Module
abstract class MainActivityModule {
    @ContributesAndroidInjector
    abstract fun injectMainActivity(): MainActivity
}

MainActivity (using DaggerAppCompatActivity):

class MainActivity : DaggerAppCompatActivity() {

    @Named("TestVM")
    @Inject
    lateinit var testVM: TestVM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

TestApplication:

class TestApplication : Application(), HasAndroidInjector {
    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.create().inject(this)
    }

    override fun androidInjector() = dispatchingAndroidInjector
}

AppComponent:

@Component(modules = [AndroidInjectionModule::class, MainActivityModule::class, ViewModelModule::class, FactoryVmModule::class])
interface AppComponent {
    fun inject(application: TestApplication)
}
qarasique
  • 173
  • 1
  • 8
  • Looks like AndroidInjection.inject(this) is used only to resolve dependencies in Activity itself. So it seems that I have to pass instance of activity through @Component.Builder to make it available in Module – qarasique May 11 '22 at 09:14

1 Answers1

2

dagger.android does do this automatically: See the explicit version of the binding that @ContributesAndroidInjector generates for you, where the generated AndroidInjector.Factory contains a @BindsInstance binding of the type you request here.

This isn't working for you because you are injecting MainActivity in a binding that is installed on your top-level component. This is a problem because AppComponent will exist before the Activity does, and will also be replaced as Android recreates the Activity: Passing an instance through @Component.Builder is not a way around this problem.

Instead, move your FactoryVmModule::class to within the subcomponent that @ContributesAndroidInjector generates, which you can do by including it in the modules attribute on @ContributesAndroidInjector. Dagger will create a different subcomponent instance per Activity instance, so your FactoryVmModule will always have a fresh binding to MainActivity.

@Module
abstract class MainActivityModule {
    @ContributesAndroidInjector(
        modules = [ViewModelModule::class, FactoryVmModule::class]
    )
    abstract fun injectMainActivity(): MainActivity
}

I moved your ViewModelModule class there as well; though it's possible you could leave it in your top-level Component if it doesn't depend on anything belonging to the Activity, you might want to keep them together. Bindings in subcomponents inherit from the application, so you can inject AppComponent-level bindings from within your Activity's subcomponent, but not the other way around. This means you won't be able to inject VM instances (here, TestVM) outside your Activity, but if they depend on the Activity, you wouldn't want to anyway: Those instances might go stale and keep the garbage collector from reclaiming your finished Activity instances.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251