I'm learning dependency injection, the following Code A is from https://developer.android.com/codelabs/android-hilt#6
It seems that Code B can also work well.
What are there different when I replace object DatabaseModule with class DatabaseModule?
Code A
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
With the provided link, you answered your own answer. As it says:
In Kotlin, modules that only contain #Provides functions can be object classes. This way, providers get optimized and almost in-lined in generated code.
So yes, you are right, both modules work, but an object Module is automatically optimized. Don't forget, that a Kotlin Object is a static class and therefore there can never be two instances of it (maybe easier for the dagger compiler?)
Related
The following code is from the project https://github.com/android/sunflower
In my mind, a abstract class can't be instanced, AppDatabase is a abstract class in Code A.
Why can Code B instance the abstract class AppDatabase.
Code A
...
abstract class AppDatabase : RoomDatabase() {
abstract fun gardenPlantingDao(): GardenPlantingDao
abstract fun plantDao(): PlantDao
companion object {
// For Singleton instantiation
#Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
...
}
Code B
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
You can't create instance of an abstract class.
Expression buildDatabase(context) actually returns instance of a class that is inherited from AppDatabase.
I suppose, this class is generated by annotation processor in the build time.
#Dima Rostapira is right, the abstract class is not really instantiated, but another class which extends from Appdatabase is generated by room. Furthermore, I would like to add, that the way you provide your db is not correct, but can be corrected the following:
#Database(entities = [YourEntityClass::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun plantDao(): PlantDao
companion object {
const val DATABASE_NAME = "app_database"
}
}
#Module
#InstallIn(SingletonComponent::class)
object DatabaseModule {
#Provides
#Singleton
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase = Room.databaseBuilder(
context,
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
).fallbackToDestructiveMigration().build()
}
I'm learning dependency injection, the following Code A is from the project https://github.com/android/sunflower
1: The parameter appDatabase of the function providePlantDao is from dependency injection, why doesn't author add #Inject before appDatabase just like Code B?
2: Is the Code C right?
Code A
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(appDatabase: AppDatabase): PlantDao {
return appDatabase.plantDao()
}
...
}
Code B
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(#Inject appDatabase: AppDatabase): PlantDao {
return appDatabase.plantDao()
}
...
}
Code C
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(): PlantDao {
#Inject lateinit var appDatabase: AppDatabase
return appDatabase.plantDao()
}
...
}
An #Inject is only needed when one wants to provide a dependency via field injection. Not only is it not possible inside functions, but you have to read the code the following:
#InstallIn(SingletonComponent::class) #Module class DatabaseModule
With this, you tell hilt that you want to create a new module that should be installed in the SingleletonComponent Graph. So every dependency provided inside this module is available to the entire application.
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase
Now you tell hilt, that you want to provide a dependency annotated with #Singleton and therefore it should be created once / always provide the same instance. Furthermore, you tell hilt, how to create an instance of AppDatabase, so at this point dagger hilt knows: "Ha, now I know how to create an AppDatabase and every time the programmer needs an AppDatabase, I will provide the same instance!"
#Provides
fun providePlantDao(appDatabase: AppDatabase): PlantDao
Now you tell hilt how to provide an instance of PlantDao. You don't need to manually inject an AppDatabase nor write #Inject because you already told hilt how to create an AppDatabase!
I used example in https://google.github.io/dagger/multibindings
Module A
#Module
object MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
Module B
#Module
object MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
component
#Component(modules = [ MyModuleA::class, MyModuleB::class])
interface MyComponent {
fun strings(): Set<String>
}
test
#Test
fun testMyComponent() {
val myComponent = DaggerMyComponent.builder().build()
println("${myComponent.strings()}")
}
It show error with MyModuleA must be set, but changing module from object to class is work fine.
#Module
class MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
#Module
class MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
#IntoSet annotation is not work in kotlin object?
I ran into the same problem and this answer solved it for me.
Dagger 2 multibindings with Kotlin
In short, you need to use Set<#JvmSuppressWildcards String> intead, because of the way that kotlin handles type variance in generics.
I'm having some weird kotlin generic issues with Dagger that I kinda fixed but the solution isn't sound.
Here's the dagger classes:
#Module class P5Module {
#Provides fun pool(): RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
#Provides
fun adapters(fusion: P5FusionAdapter, personas: P5ListAdapter, skills: P5SkillsAdapter, info: InfoAdapter)
: List<Pageable> = listOf(fusion, personas, skills, info)
}
#ActivityScope
#Subcomponent(modules = arrayOf(P5Module::class)) interface P5Component {
fun adapter(): PageableAdapter
}
interface Pageable {
fun manager(ctx: Context): LayoutManager
fun attach()
fun adapter(): Adapter<*>
}
class PageableAdapter
#Inject constructor(val pageables: List<Pageable>, val pool: RecyclerView.RecycledViewPool) :PagerAdapter()
When I build, I get this kapt error in the top level component:
e: C:\Users\daykm\StudioProjects\P5Executioner\app\build\tmp\kapt3\stubs\appDebug\com\daykm\p5executioner\AppComponent.java:17: error: [com.daykm.p5executioner.main.P5Component.adapter()] java.util.List<? extends com.daykm.p5executioner.view.Pageable> cannot be provided without an #Provides-annotated method.
e:
e: public abstract interface AppComponent {
e: ^
e: java.util.List<? extends com.daykm.p5executioner.view.Pageable> is injected at
e: com.daykm.p5executioner.view.PageableAdapter.<init>(pageables, …)
e: com.daykm.p5executioner.view.PageableAdapter is provided at
e: com.daykm.p5executioner.main.P5Component.adapter()
e: java.lang.IllegalStateException: failed to analyze: org.jetbrains.kotlin.kapt3.diagnostic.KaptError: Error while annotation processing
I took a look at the stubs generated:
#javax.inject.Inject()
public PageableAdapter(#org.jetbrains.annotations.NotNull()
java.util.List<? extends com.daykm.p5executioner.view.Pageable> pageables, #org.jetbrains.annotations.NotNull()
android.support.v7.widget.RecyclerView.RecycledViewPool pool) {
super();
}
#org.jetbrains.annotations.NotNull()
#dagger.Provides()
public final java.util.List<com.daykm.p5executioner.view.Pageable> adapters(#org.jetbrains.annotations.NotNull()
Apparently doesn't match with because when I modify the dagger class like so:
#Module class P5Module {
#Provides fun pool(): RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
#Provides
fun adapters(fusion: P5FusionAdapter, personas: P5ListAdapter, skills: P5SkillsAdapter, info: InfoAdapter)
: List<*> = listOf(fusion, personas, skills, info)
}
#ActivityScope
#Subcomponent(modules = arrayOf(P5Module::class)) interface P5Component {
fun adapter(): PageableAdapter
}
interface Pageable {
fun manager(ctx: Context): LayoutManager
fun attach()
fun adapter(): Adapter<*>
}
class PageableAdapter
#Inject constructor(val pageables: List<*>, val pool: RecyclerView.RecycledViewPool) :PagerAdapter()
The issue disappears.
Am I bumping into some weird translation issue with covariance and contravariance? I'd rather not force a cast downstream.
Kotlin's List<out E> translates to covariance in Java's List<? extends E> by default. Dagger doesn't like that.
To get around this issue, you can either switch to MutableList<E> which is invariant, or write List<#JvmSuppressWildcards E>.
I've started using Dagger 2 and faced strange issue.
I have 4 modules, 3 of them in ApplicationComponent and the other one has different Scope (UsersScope).
Problem with injecting UsersInteractor into UsersPresenter
Error:[BlankFragment)] com.interactors.UsersInteractor cannot be provided without an #Inject constructor or from an #Provides-annotated method.
Here is my classes
#Singleton
#Component(modules = arrayOf(ApplicationModule::class, NetworkModule::class, DbModule::class))
interface ApplicationComponent {
fun plusUsersComponent(usersModule: UsersModule): UsersComponent
}
#Module
class ApplicationModule(private val context: Context) {
#Provides
#Singleton
fun provideContext(): Context = context
}
#Subcomponent(modules = arrayOf(UsersModule::class))
#UsersScope
interface UsersComponent {
fun inject(blankFragment: BlankFragment)
}
#Module
class UsersModule {
#Provides
#UsersScope
fun provideUsersRepository(restService: RestService, dbService: DbService): IUsersRepository =
UsersRepository(restService, dbService)
#Provides
#UsersScope
fun provideUsersInteractor(usersRepository: UsersRepository): IUsersInteractor =
UsersInteractor(usersRepository)
#Provides
#UsersScope
fun provideUsersPresenter(usersInteractor: UsersInteractor): IUsersPresenter =
UsersPresenter(usersInteractor)
}
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class UsersScope
There is no UsersInteractor known to Dagger since you only provide IUsersInteractor.
Switch your presenter provider method to use IUsersInteractor and it should work
fun provideUsersPresenter(usersInteractor: IUsersInteractor)