Android dagger 2: dependency cannot be provided without an #Provides-annotated method - android

Dagger 2 dependencies:
implementation "com.google.dagger:dagger:2.15"
kapt "com.google.dagger:dagger-compiler:2.15"
My AppComponent:
#Singleton
#Component(modules = [
DomainModule::class,
DataModule::class,
PresentationModule::class,
ViewModelModule::class,
RepositoriesModule::class
])
interface AppComponent {
//reps
fun topicsRep(): TopicsRepository
fun countriesRep(): CountriesRepository
fun loginRep(): LoginRepository
}
My ViewModelModule class:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
internal abstract fun loginViewModel(viewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(CountriesViewModel::class)
internal abstract fun countriesViewModel(viewModel: CountriesViewModel): ViewModel
}
My ViewModelFactory:
#Suppress("UNCHECKED_CAST")
#Singleton
class ViewModelFactory
#Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
And for example my CountryComponent and CountryModule:
#ActivityScope
#Component(modules = [CountryModule::class], dependencies = [AppComponent::class])
interface CountryComponent {
fun inject(activity: SelectCountryActivity)
}
#Module
class CountryModule {
#Provides
#Singleton
fun provideCountriesInteractor(rep: CountriesRepository)
= SelectCountryInteractor(rep)
}
What i am trying to achieve - i need to inject an instance of ViewModelFactory to my activities and fragments. My viewmodels contains others dependensies. Also trying to separate dependencies for each screen.
After build getting error:
CountryComponent.java:10: error: java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method
java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at ViewModelFactory.<init>(viewModels)
ViewModelFactory is injected at SelectCountryActivity.factory
SelectCountryActivity is injected at CountryComponent.inject(activity)
When setting ViewModelFactory as #Singleton getting error:
CountryComponent scoped with #ActivityScope may not reference bindings with different scopes:
#dagger.Component(modules = {CountryModule.class}, dependencies = {AppComponent.class})
#Singleton class ViewModelFactory
AppComponent.java:6: error: AppComponent scoped with #Singleton may not reference bindings with different scopes:
#dagger.Component(modules = {DomainModule.class, DataModule.class, PresentationModule.class, ViewModelModule.class, RepositoriesModule.class})
Is there any reason to mark ViewModel as Singleton or ActivityScope?

Fixed by adding
fun viewModelFactory(): ViewModelFactory
to my AppComponent.
Also removed #Singleton from ViewModelFactory

Related

Error with injecting view model using Dagger 2

I just started learning Dagger 2 and trying to make subcomponent for subcomponent. I faced error and can't find where it comes from. Below I will write my dagger structure.
AppComponent
-AppModule
-SubcomponentsModule
--MainComponent
---MainModule
----AuthorizationComponent
-----AuthorizationModule
----UserProfileComponent
-----UserProfileModule
---ViewModelModule
-NetworkModule
Application component:
#Singleton
#Component(modules = [AppModule::class, SubcomponentsModule::class, NetworkModule::class])
interface AppComponent {
fun mainComponent(): MainComponent.Factory
}
It has SubcomponentsModule with one subcomponent so far:
#Module(subcomponents = [MainComponent::class])
class SubcomponentsModule
MainComponent:
#Subcomponent(modules = [MainModule::class, ViewModelModule::class])
interface MainComponent {
#Subcomponent.Factory
interface Factory {
fun create(): MainComponent
}
fun inject(activity: MainActivity)
fun authorizationComponent(): AuthorizationComponent.Factory
fun userProfileComponent(): UserProfileComponent.Factory
}
ViewModelModule:
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
MainComponent has MainModule with AuthorizationComponent and UserProfileComponent:
#Module(subcomponents = [AuthorizationComponent::class, UserProfileComponent::class])
abstract class MainModule {
#Binds
#IntoMap
#ViewModelKey(MainViewModel::class)
abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
}
AuthorizationComponent with AuthorizationModule:
#Subcomponent(modules = [AuthorizationModule::class])
interface AuthorizationComponent {
#Subcomponent.Factory
interface Factory {
fun create(): AuthorizationComponent
}
fun inject(fragment: AuthorizationFragment)
}
AuthorizationModule:
#Module
abstract class AuthorizationModule {
#Binds
#IntoMap
#ViewModelKey(AuthorizationViewModel::class)
abstract fun authorizationViewModel(viewModel: AuthorizationViewModel): ViewModel
}
UserProfileComponent with UserProfileModule is equivalent to AuthorizationComponent and AuthorizationModule.
When I launch build, I get error:
error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
ui.base.ViewModelFactory(viewModels)
ui.base.ViewModelFactory is injected at
di.main.ViewModelModule.bindViewModelFactory(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
ui.base.BaseActivity.viewModelFactory
ui.main.MainActivity is injected at
di.main.MainComponent.inject(ui.main.MainActivity) [di.AppComponent ? di.main.MainComponent]
The following other entry points also depend on it:
di.main.auth.AuthorizationComponent.inject(ui.auth.AuthorizationFragment) [di.AppComponent ? di.main.MainComponent ? di.main.auth.AuthorizationComponent]
di.main.userprofile.UserProfileComponent.inject(ui.userprofile.UserProfileFragment) [di.AppComponent ? di.main.MainComponent ? di.main.userprofile.UserProfileComponent]
The annotation I use for view models search is:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
And the last thing that should be mentioned here is my ViewModelFactory:
#Singleton
class ViewModelFactory #Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}

cannot be provided without an #Provides-annotated method, A binding with matching key exists in component

I'm trying to add dagger2 in a seed project for learn pourposes (I'm not an expert), but i havving the same problem:
e: /Users/foca/projects/personalProjects/bar-droid-application/bar-droid/app/build/tmp/kapt3/stubs/debug/com/bar/bar_droid/di/AppComponent.java:8: error: [Dagger/MissingBinding] com.bar.bar_droid.domain.repository.RegisterRepository cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector {
^
A binding with matching key exists in component: com.bar.bar_droid.ui.accessflow.di.AccessScreenProvider_ProvideRoleSelectorFragmentFactory.RoleSelectionFragmentSubcomponent
com.bar.bar_droid.domain.repository.RegisterRepository is injected at
com.bar.bar_droid.domain.interactor.userregistration.UserRegistrationUseCase(registerRepository, …)
com.bar.bar_droid.domain.interactor.userregistration.UserRegistrationUseCase is injected at
com.bar.bar_droid.ui.accessflow.roleselector_fragment.viewmodel.RoleSelectionViewModel(…, userRegistrationUseCase)
com.bar.bar_droid.ui.accessflow.roleselector_fragment.viewmodel.RoleSelectionViewModel is injected at
com.bar.bar_droid.ui.di.ViewModelModule.provideRoleSelectionViewModel(roleSelectionViewModel)
java.util.Map,javax.inject.Provider> is injected at
com.bar.bar_droid.utils.mvvm.ViewModelFactory(creators)
com.bar.bar_droid.utils.mvvm.ViewModelFactory is injected at
com.bar.bar_droid.ui.accessflow.loginselector_fragment.LoginFragment.viewModelFactory
com.bar.bar_droid.ui.accessflow.loginselector_fragment.LoginFragment is injected at
dagger.android.AndroidInjector.inject(T) [com.bar.bar_droid.di.AppComponent → com.bar.bar_droid.di.ActivityBuilderModule_BindAccessActivity.AccessActivitySubcomponent → com.bar.bar_droid.ui.accessflow.di.AccessScreenProvider_ProvideLoginSelectorFragmentFactory.LoginFragmentSubcomponent]
It is also requested at:
com.bar.bar_droid.domain.interactor.userregistration.UserRegistrationUseCase(registerRepository, …)
The following other entry points also depend on it:
dagger.android.AndroidInjector.inject(T) [com.bar.bar_droid.di.AppComponent → com.bar.bar_droid.di.ActivityBuilderModule_BindAccessActivity.AccessActivitySubcomponent → com.bar.bar_droid.ui.accessflow.di.AccessScreenProvider_ProvideMailPasswordFragmentFactory.MailPasswordSignUpFragmentSubcomponent]
dagger.android.AndroidInjector.inject(T) [com.bar.bar_droid.di.AppComponent → com.bar.bar_droid.di.ActivityBuilderModule_BindMainActivity.MainActivitySubcomponent → com.bar.bar_droid.ui.mainflow.di.MainFlowProvider_ProvideMenuFragmentFactory.MenuFragmentSubcomponent]
Here my Code:
Application:
class MainApplication : Application(), HasAndroidInjector {
#Inject lateinit var androidInjector : DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
// Starts Dagger
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
}
override fun androidInjector(): AndroidInjector<Any> = androidInjector
}
Dagger Component:
#Singleton
#Component(
modules = [AndroidSupportInjectionModule::class,
ApplicationModule::class,
RoomDatabaseModule::class,
FirebaseModule::class,
GoogleModule::class,
ActivityBuilderModule::class]
)
interface AppComponent : AndroidInjector<MainApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
ActivityBuilderModule:
#Module
abstract class ActivityBuilderModule {
#PerActivity
#ContributesAndroidInjector(modules = [AccessScreenProvider::class, AccessScreenDependencyProvider::class])
abstract fun bindAccessActivity(): AccessActivity
#PerActivity
#ContributesAndroidInjector(modules = [MainFlowProvider::class])
abstract fun bindMainActivity(): MainActivity
}
Fragments for AccessActivity:
#Module
abstract class AccessScreenProvider {
#PerFragment
#ContributesAndroidInjector(modules = [ViewModelModule::class, LoginSelectorProvider::class])
abstract fun provideLoginSelectorFragmentFactory(): LoginFragment
#PerFragment
#ContributesAndroidInjector(modules = [ViewModelModule::class])
abstract fun provideMailPasswordFragmentFactory(): MailPasswordSignUpFragment
#PerFragment
#ContributesAndroidInjector(modules = [ViewModelModule::class, RoleSelectionProvider::class])
abstract fun provideRoleSelectorFragmentFactory(): RoleSelectionFragment
}
ViewModelModule:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
#Module
abstract class ViewModelModule {
#Binds
abstract fun provideViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#PerFragment
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun provideLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#PerFragment
#IntoMap
#ViewModelKey(MailPasswordSignUpViewModel::class)
abstract fun provideMailPasswordSighUpViewModel(mailPasswordSignUpViewModel: MailPasswordSignUpViewModel): ViewModel
#Binds
#PerFragment
#IntoMap
#ViewModelKey(RoleSelectionViewModel::class)
abstract fun provideRoleSelectionViewModel(roleSelectionViewModel: RoleSelectionViewModel): ViewModel
}
And finally for RoleSelectionProvide:
#Module
class RoleSelectionProvider {
#Provides
#PerFragment
fun provideRealAuthDataSource(fireBaseAuth: FirebaseAuth): UserAuthDataSource = RealUserAuthDataSource(fireBaseAuth)
#Provides
#PerFragment
fun provideRegisterRepository(realUserAuthDataSource: RealUserAuthDataSource, userProfileDao: UserProfileDao): RegisterRepository {
return RegisterRepositoryImpl(realUserAuthDataSource, userProfileDao)
}
}
The only place that I'm injecting RegisterRepositoryImpl is on UserRegistrationUseCase, I don't have any idea about what I'm doing wrong.
It is strange that you are using it in two places: ViewModelModule. It is most probably the problem and the duplication of keys error. "A binding with matching key exists in component:"
Also in provideMailPasswordFragmentFactory you are passing only ViewModelModule, but in
provideRoleSelectorFragmentFactory you are passing both ViewModelModule and also RoleSelectionProvider. In the first case, you are missing the dependencies provided by RoleSelectionProvider which provides the Repository.
#PerFragment
#ContributesAndroidInjector(modules = [ViewModelModule::class])
abstract fun provideMailPasswordFragmentFactory(): MailPasswordSignUpFragment
#PerFragment
#ContributesAndroidInjector(modules = [ViewModelModule::class, RoleSelectionProvider::class])
abstract fun provideRoleSelectorFragmentFactory(): RoleSelectionFragment

Injecting ViewModelFactory in different activities

I'm using the well-known Dagger-ViewModelFactory pattern to be able to inject a factory for all the ViewModel in all the activities.
#ActivityScope
class ViewModelFactory #Inject constructor(
private val creators: MutableMap<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return creator.get() as T
}
}
The problem I have is that when I inject the factory into an Activity Dagger fails because the providers of the objects for the ViewModels that I'm not going to use are not always accessible. They are not because the modules that contain the providers have not been added.
For example, I have a LogIn activity and a SignUp activity, and this is the way I add the subcomponents for them:
#ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
LogInModule::class,
BindLogInModule::class
])
#ActivityScope
internal abstract fun loginActivityInjector(): LoginActivity
#ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
#ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity
Please notice that when I create the subcomponent for SignUpActivity I do not add the Module LogInModule because I do not need the bindings in that Module.
The result is that I get the error
e: com.package.my.AppComponent.java:8: error: [Dagger/MissingBinding] com.package.my.login.domain.LogInAuthenticator cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector {
^
A binding with matching key exists in component: com.package.my.di.ActivityInjectorsModule_LoginActivityInjector$app_prodDebug.LoginActivitySubcomponent
com.package.my.login.domain.LogInAuthenticator is injected at
com.package.my.login.repository.LoginRepository(logInAuthenticator)
com.package.my.login.repository.LoginRepository is injected at
com.package.my.login.domain.LoginUseCase(loginRepository)
com.package.my.login.domain.LoginUseCase is injected at
com.package.my.login.presentation.LoginViewModel(loginUseCase)
com.package.my.login.presentation.LoginViewModel is injected at
com.package.my.di.ViewModelModule.provideLoginViewModel(viewModel)
java.util.Map,javax.inject.Provider> is injected at
com.package.my.di.ViewModelFactory(creators)
com.package.my.di.ViewModelFactory is injected at
com.package.my.di.ViewModelModule.bindViewModelFactory$app_prodDebug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.package.my.login.ui.SignUpActivity.viewModelFactory
com.package.my.login.ui.SignUpActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.package.my.di.AppComponent → com.package.my.di.ActivityInjectorsModule_SignUpActivityInjector$app_prodDebug.SignUpActivitySubcomponent]
This happens because LogInAuthenticator is provided by LogInModule.
Does this mean that the only solution is to add LogInModule even if I don't really need to create GoogleSignInClient in the SignUpActivity?
You have declared both of #ContributesAndroidInjector methods to be dependent on ViewModelModule. Inside ViewModelModule you have declared all of the ViewModels out there, which means, that at the point when Dagger wants to construct the dependency tree for SignUpActivity it will also require you to explicitly mention how LoginViewModel should be constructed. This happens, because Dagger needs to know how each of the dependency declared inside ViewModelModule should be constructed.
The solution for you case will be either include all of the modules in all of #ContributesAndroidInjector declarations (which is an ugly approach), or, alternatively, move the provider method of SignUpViewModel to SignUpModule and do not include ViewModelModule for SignUpActivity declaration.
Here's the setup that works for me.
First, I have created a BaseActivityModule, which all of feature modules should include in their dedicated #Module classes:
#Module
abstract class BaseActivityModule {
#Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
Then, assuming we have 2 features: Foo and Bar:
#Module
abstract class ActivitiesModule {
#PerActivity #ContributesAndroidInjector(modules = [FooModule::class])
abstract fun contributesFooActivity(): FooActivity
#PerActivity #ContributesAndroidInjector(modules = [BarModule::class])
abstract fun contributesBarActivity(): BarActivity
}
The implementation class of ViewModelProvider.Factory should be scoped with #PerActivity because the same instance of ViewModelProvider.Factory should be provided each time that dependency is needed to be injected in the scope of particular activity:
private typealias ViewModelProvidersMap = Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
#PerActivity
class MyViewModelFactory #Inject constructor(
private val creators: ViewModelProvidersMap
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var viewModelProvider = creators[modelClass]
if (viewModelProvider == null) {
val entries = creators.entries
val mapEntry = entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
} ?: throw IllegalArgumentException("Unknown model class $modelClass")
viewModelProvider = mapEntry.value
}
try {
#Suppress("UNCHECKED_CAST")
return viewModelProvider.get() as T
} catch (e: Throwable) {
throw IllegalArgumentException("Couldn't create ViewModel with specified class $modelClass", e)
}
}
}
Where #PerActivity is declared this way:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class PerActivity
FooModule and BarModule are declared as such:
#Module(includes = [BaseActivityModule::class])
abstract class FooModule {
#Binds #IntoMap #ViewModelKey(FooViewModel::class)
abstract fun bindsFooViewModel(viewModel: FooViewModel): ViewModel
}
#Module(includes = [BaseActivityModule::class])
abstract class BarModule {
#Binds #IntoMap #ViewModelKey(BarViewModel::class)
abstract fun bindsBarViewModel(viewModel: BarViewModel): ViewModel
}
Then we are including ActivitiesModule in the AppComponent as such:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivitiesModule::class
])
interface AppComponent {
...
}
With this approach we've moved the ViewModelProvider.Factory creation one layer down: previously it was in the topmost AppComponent and now each of subcomponents will take care of creating the ViewModelProvider.Factory.
The answer to why you need to add the LoginModule lies in your error log. The error log traces the dependencies as below:
SignUpActivity <--is injected at-- ViewModelFactory <--is injected at-- LoginViewModel <--is injected at-- LoginUseCase <--is injected at-- LoginRepository <--is injected at-- LoginAuthenticator
The map above shows that you need to add LoginModule because Dagger needs it to successfully inject your ViewModelFactory into your SignUpActivity.
EDIT
Move SignUpViewModel binding from your ViewModelModule and place it in your SignUpModule like below:
#Module(includes = [SignUpModule.BindsModule::class])
class SignUpModule {
// your other provides methods
#Module
interface BindsModule{
#Binds
#IntoMap
#ViewModelKey(SignUpViewModel::class)
fun signUpViewModel(signUpViewModel: SignUpViewModel): ViewModel
}
}
Then add subcomponents this way. Notice ViewModelModule has been excluded
#ContributesAndroidInjector(modules = [
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
#ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity

Dagger can't inject a parameter of type interface on a ViewModel's constructor using Dagger Android

Why can't I inject interface types in ViewModel constructors when using Dagger Android?
Here's my AppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelModule::class
]
)
interface AppComponent {
fun inject(app: App)
}
Here's the module for my activities:
#Module
abstract class ActivityBuilder {
#ActivityScope
#ContributesAndroidInjector(modules = [UserDetailsModule::class])
abstract fun userDetailsActivity(): UserDetailsActivity
}
Here's the UserDetailsModule
#Module
abstract class UserDetailsModule {
#Binds
#ActivityScope
abstract fun providesUserRepository(repository: UserRepositoryImpl): UserRepository
}
Here's the ViewModelModule where I follow the dynamic view model factory solution.
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(UserDetailsViewModel::class)
abstract fun userDetailsViewModel(viewModel: UserDetailsViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Here's the concrete class of UserRepository
class UserRepositoryImpl #Inject constructor(private val api: Api) : UserRepository { ... }
Here's the UserDetailsViewModel
class UserDetailsViewModel #Inject constructor(private val userRepository: UserRepository) : ViewModel() { ... }
When I compile, it will error
UserRepository cannot be provided without an #Provides-annotated method.
However, the confusing part is when I change UserDetailsViewModel's constructor to receive UserRepositoryImpl instead of type UserRepository, it compiles successfully and it works.
Anyone knows what the problem might be?
Solved the issue. Using a generic view model factory found in this Github issue:
https://github.com/google/dagger/issues/1273#issuecomment-447997439

Inject ViewModel with Dagger2

I am building an Android application with Dagger2 and the new Architecture Components. I have tried to make ViewModels injectable following this tutorial: https://blog.kotlin-academy.com/understanding-dagger-2-multibindings-viewmodel-8418eb372848
When I run my code, I get the following error:
[AndroidInjector.inject(T) Map<Class<? extends ViewModel>, Provider<ViewModel>> cannot be provided without an #Provides-annotated method.
My code looks like this:
#Singleton
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass]
?: creators.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("Unknown model class $modelClass")
return creator.get() as T
}
}
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
#MapKey
#Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
annotation class ViewModelKey(val value: KClass<out ViewModel>)
I the module is also included in the AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ViewModelFactoryModule::class // ...
])
interface AppComponent : AndroidInjector<BaseApplication>
class BaseApplication : DaggerApplication(), HasActivityInjector {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().appModule(AppModule(this)).build()
}
The ViewModel I try to inject is declared in a Module like this:
#Module
abstract class SettingsModule {
#Binds
#IntoMap
#ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(model: SettingsViewModel): ViewModel
}
Does anybody have an idea what the problem could be?
It's hard to point exact issue with Dagger. Try moving the
#Binds
#IntoMap
#ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(model: SettingsViewModel): ViewModel
from SettingsModule to ViewModelFactoryModule as that's what I am doing in my code-base and it's working.
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory):
ViewModelProvider.Factory
}
OR
Try changing your ViewModelKey to this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

Categories

Resources