google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.45k stars 2.02k forks source link

Interface type cannot be provided without an @Provides-annotated method using Factory #1800

Closed aloh86 closed 4 years ago

aloh86 commented 4 years ago

I have a repository class that has an Android Room Persistence Library DAO object as a dependency. DAO's are interfaces, but they are not user implemented. A room database object provides an implementation of a DAO through a call like:

roomDatabase().getDatabaseSingleton(context).myDao()

Is it possible to provide the DAO without creating a module with a constructor that takes a Room database object? I've tried using @Subcomponent.Factory like this:

@Subcomponent.Factory
    interface Factory {
        fun create(????): LoginComponent
    }

I'm not sure what to pass in as a parameter to create. I've tried passing in a Dao Module like this:

// DaoModule.kt
@Module
class DaoModule(private val db: MyDatabase) {

    @Provides
    fun providesAccountDao(): AccountDao {
        return db.AccountDao()
    }
}

// LoginComponent.kt
@Subcomponent(modules = [DaoModule::class])
interface LoginComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(daoModule: DaoModule): LoginComponent
    }

    fun inject(activity: LoginFragment)
}

However, with this I get the error:

AccountDao cannot be provided without an @Provides-annotated method.

I've also tried the following by providing the DAO myself after getting a database instance:

// LoginComponent.kt
@Subcomponent
interface LoginComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(@BindsInstance accountDao: AccountDao): LoginComponent
    }

// LoginFragment.kt
loginComponent = (activity?.application as ApparentApplication).appComponent.loginComponent().create(db.accountDao())

But I still get the error, AccountDao cannot be provided without an @Provides-annotated method.

How do I provide this using Factory?

dmapr commented 4 years ago

You can provide it like so:

@Component(
    modules = [
        AndroidSupportInjectionModule::class,
        MyAppModule::class,
        PersistenceModule::class
    ]
)
@Singleton
interface MyAppComponent : AndroidInjector<MyApplication> {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance myApplication: MyApplication) : MyAppComponent
    }
}

@Module
interface MyAppModule {
    @Binds
    @Singleton
    fun application(myApplication: MyApplication): Application
}

@Module
object PersistenceModule {
    @Provides
    @Singleton
    fun myDatabase(application: Application): MyDatabase {
        return Room
            .databaseBuilder(application, MyDatabase::class.java, "my.db")
            .build()
    }

    @Provides
    fun myDao(database: MyDatabase) : MyDao = database.myDao()
}

No need for additional factories or passing the database into a module constructor

aloh86 commented 4 years ago

Sorry about closing and reopening the issue. My 1 year old started mashing my keyboard and closed it for me :(

Thanks, dmapr. Out of curiosity, can this be done by passing in just the dao as a parameter to a Factory's create method? Or is it as the error says, cannot be done without an @Provides annotated method?

dmapr commented 4 years ago

Well, yes, you can do that if you want to. But no matter how you go about it you have to write the code that makes the myDatabase.myDao() call. If you defined a factory you could give the myDao as the parameter to create method. Something like this:

@Subcomponent
interface MyDaoComponent {
  @Subcomponent.Factory
  interface Factory {
    fun create(@BindsInstance myDao: MyDao) : MyDaoComponent
  }

  val something: Something
}

// Dependency
class Something @Inject constructor(myDao: MyDao) {
  …
}

then to use it

@Inject lateinit var myDatabase: MyDatabase
@Inject lateinit var myDaoComponentFactory: MyDaoComponent.Factory
…

val myDaoComponent = myDaoComponentFactory.create(myDatabase.myDao())

// myDaoComponent.something is initialized