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)
Related
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'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?)
I'm trying MVVM, Dagger2, Retrofit and Coroutine. Now I have the problem that I can successfully inject a ProfileService into my activity, but not into my repository. I get a profileService lateinit property has not been initialized
//MainActivity
#Inject
lateinit var profileService: ProfileService //only for testing
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getTheExpectedResult = profileService.hasValidName("asdffff") //true
profileViewModel.createProfile("ku") //throw the not initialized
}
The viewmodel calls the repository.
// profile repository
private fun getProfileRepository(userId: String = "", apiKey: String = ""): ProfileRepository {
return ProfileRepository(ApiFactory.getApi(userId, apiKey))
}
fun createProfile(name: String) {
scope.launch {
try {
val profile = getProfileRepository().createProfile(name)
profileLiveData.postValue(profile)
}
//...
In the repository I inject the profileService
class ProfileRepository(private val api: NIPApiInterface) {
#Inject
lateinit var profileService: ProfileService
suspend fun createProfile(name: String): ProfileResponse? {
if (!profileService.hasValidName(name)) { //throw the not initialized
//...
My unspectacular ProfileService
class ProfileService #Inject constructor() {
fun hasValidName(name: String): Boolean {
return name.length > 3
}
}
So that I don't post too much code, the following info. In my Application I seem to initialize everything correctly, because the Activity can access the ProfileService. Here are my Dagger configurations:
//AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class
]
)
interface AppComponent : AndroidInjector<NIPApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: NIPApplication): AppComponent
}
}
The AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app: NIPApplication): Context = app
}
If you need more code, please send a comment.
Dagger won't inject dependencies unless it creates an instance of a class that needs injection or is explicitly asked to. Since the ProfileRepository object is created by you, it won't get its dependencies injected by Dagger.
The best way to solve this is to let Dagger create ProfileRepository objects by #Inject-annotated constructor (or using #Provides-annotated method in a Dagger module (provider), however this seems quite redundant in this particular case):
class ProfileRepository #Inject constructor(
private val api: NIPApiInterface,
private val profileService
)
Note that now Dagger will want to inject an NIPApiInterface object as well, so you have to create a provider for that or remove it from the constructor and pass it in some other way.
for my current project I'm using Kotlin and Dagger 2.
I want to inject dependencies in an secondary constructor, but the constructor never gets initialized.
class SelectionFragmentModel ():ViewModel(){
lateinit var channelInfosRepository: ChannelInfosRepository
#Inject constructor(channelInfosRepository: ChannelInfosRepository) : this(){
this.channelInfosRepository = channelInfosRepository
}
...
}
As a workaround I'm currently injecting in the primary constructor but this isn't optimal.
class SelectionFragmentModel #Inject constructor(private val channelInfosRepository: ChannelInfosRepository):ViewModel(){
constructor() : this(ChannelInfosRepository())
...
}
Am I missing something?
Make sure your SelectionFragmentModel class has only one constuctor. It does not matter if the constructor is primary or secondary in terms of Kotlin language idioms. There will be only one constructor to use in SelectionFragmentModel.
The following code leaves no options for the initializer regarding which constructor to use as there is only one!
class SelectionFragmentModel: ViewModel {
lateinit var channelInfosRepository: ChannelInfosRepository
#Inject constructor(channelInfosRepository: ChannelInfosRepository) : super() {
this.channelInfosRepository = channelInfosRepository
}
}
Example (that works)
In this example we have default setup using dagger:
#Module annotated class that provides us with ChannelInfosRepository;
Modified SelectionFragmentModel (the code is located above the example);
Interface annotated with #Component with a list of modules consisting of only one module;
Everything else is just Android stuff.
Here is the module:
#Module
class AppModule {
#Provides
#Singleton
fun providesChannelInfosRepository(): ChannelInfosRepository {
return ChannelInfosRepository()
}
}
Interface annotated with #Component:
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(fragment: MainFragment)
}
Here is how the AppComponent is instantiated. MyApplication must be mentioned in AndroidManifest.xml.
class MyApplication : Application() {
var appComponent: AppComponent? = null
private set
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.appModule(AppModule())
.build()
}
}
Fragment that injects SelectionFragmentModel using AppComponent:
class MainFragment : Fragment() {
#Inject
lateinit var selectionFragmentModel: SelectionFragmentModel
companion object {
fun newInstance() = MainFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Ugly but fine for a test
(activity?.application as? MyApplication)?.appComponent?.inject(this)
}
// onCreateView and other stuff ...
#SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
println("ViewModel address = ${selectionFragmentModel}")
println("ChannelInfosRepository address = ${selectionFragmentModel.channelInfosRepository}")
}
}
The result (instead of printing the result I displayed it in a TextView):
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>.