Why can't dagger process these kotlin generics? - kotlin

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

Related

Why can a abstract class be instance in Kotlin?

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()
}

Should I add #Inject for the function parameter which is from dependency injection?

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!

What are there different when I replace object DatabaseModule with class DatabaseModule?

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?)

#Provides and #Binds methods in same class Kotlin

After dagger 2.11 we can use a #Binds annotation and mark our Module as abstract in this case which is more efficient than a concrete.
If my Module has both #Provides and #Binds methods, I have two options :
Simplest would be to mark your #Provides instance methods as static.
If it is necessary to keep them as instance methods, then you can
split your module into two and extract out all the #Binds methods
into an abstract Module.
The second option works fine in Java and Kotlin but the first option works fine in Java but I don't know how to implement the same in Kotlin. If I move #Provides method to Companion object it throw Error:(30, 1) error: #Provides methods can only be present within a #Module or #ProducerModule.
How can do this in Kotlin.
Second Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.Declarations::class])
abstract class ApplicationModule {
#Module
internal interface Declarations {
#Binds
fun bindContext(application: Application): Context
}
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
First Option:(Not working)
ApplicationModule.kt
#Module
abstract class ApplicationModule {
//expose Application as an injectable context
#Binds
internal abstract fun bindContext(application: Application): Context
companion object {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Generated Java file for first option:
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\b\'\u0018\u0000 \b2\u00020\u0001:\u0001\bB\u0005\u00a2\u0006\u0002\u0010\u0002J\u0015\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006H!\u00a2\u0006\u0002\b\u0007\u00a8\u0006\t"}, d2 = {"Lio/mywebsie/di/ApplicationModule;", "", "()V", "bindContext", "Landroid/content/Context;", "application", "Landroid/app/Application;", "bindContext$app_debug", "Companion", "app_debug"})
#dagger.Module()
public abstract class ApplicationModule {
public static final io.mywebsie.di.ApplicationModule.Companion Companion = null;
#org.jetbrains.annotations.NotNull()
#dagger.Binds()
public abstract android.content.Context bindContext$app_debug(#org.jetbrains.annotations.NotNull()
android.app.Application application);
public ApplicationModule() {
super();
}
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public static final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002\u00a2\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007\u00a8\u0006\u0005"}, d2 = {"Lio/mywebsie/di/ApplicationModule$Companion;", "", "()V", "provideMvpStarterService", "Lio/mywebsie/data/remote/MvpStarterService;", "app_debug"})
public static final class Companion {
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
private Companion() {
super();
}
}
}
Update:
Thanks to #David Medenjak the link you provided in comment made everything clear I came across two ways to achieve the first option.
Updated code:
First Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.AModule::class])
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
object AModule {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
or
#Module
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
companion object {
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Both works just fine but for some reason the first option does not look appealing to me so I prefer the second option.
Here's an example code to demonstrate how to use Binds and Provides annotated methods in a single Kotlin class:
#Module
abstract class MessagesPresentationModule {
#Module
companion object {
const val MESSAGES = 0x00
#JvmStatic
#Provides
fun provideRecyclerAdapter(
itemComparator: DisplayItemComperator,
factoryMap: Map<Int, ViewHolderFactory>,
binderMap: Map<Int, ViewHolderBinder>,
androidPreconditions: AndroidPreconditions
): RecyclerViewAdapter {
return RecyclerViewAdapter(
itemComperator = itemComparator,
viewHolderFactoryMap = factoryMap,
viewBinderFactoryMap = binderMap,
androidPreconditions = androidPreconditions
)
}
}
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewModelFactory(factory: MessagesViewHolder.MessageViewHolderFactory): ViewHolderFactory
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewHolderBinder(binder: MessagesViewHolder.MessagesViewHolderBinder): ViewHolderBinder
}

Kotlin Dagger 2 cannot provide Interactor

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)