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>)
Related
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
}
I’m trying to create dependency injection of my ViewModel using Dagger2 with multi binds but I’m receiving this error and I can’t make it work, I tried several answers (below) but none of them helped me.
This is the error I receive :
SaveMyHeroApplicationComponent.java:8: 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.
And this is my code
class SaveMyHeroApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerSaveMyHeroApplicationComponent.factory().create(this)
}
}
#Singleton
#Component(modules = [AndroidInjectionModule::class, MainActivityModule::class])
interface SaveMyHeroApplicationComponent : AndroidInjector<SaveMyHeroApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): SaveMyHeroApplicationComponent
}
}
#Module(includes = [NetworkModule::class, HomeModule::class])
class MainActivityModule {
#Provides
fun provideViewModelFactoryProviders(
providers: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory = SaveMyHeroViewModelFactory(providers)
}
class SaveMyHeroViewModelFactory(
private val providers: Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
requireNotNull(getProvider(modelClass).get()) {
"Provider for $modelClass returned null"
}
private fun <T : ViewModel> getProvider(modelClass: Class<T>): Provider<T> =
try {
requireNotNull(providers[modelClass] as Provider<T>) {
"No ViewModel provider is bound for class $modelClass"
}
} catch (error: ClassCastException) {
error("Wrong provider type registered for ViewModel type $error")
}
}
#Module(includes = [HomeModule.ProvideViewModel::class])
abstract class HomeModule {
#ContributesAndroidInjector(modules = [InjectViewModel::class])
abstract fun bind(): HomeFragment
#Module
class ProvideViewModel {
#Provides
#IntoMap
#ViewModelKey(HomeViewModel::class)
fun provideHomeViewModel() = HomeViewModel()
}
#Module
class InjectViewModel {
#Provides
fun provideHomeViewModel(
factory: ViewModelProvider.Factory,
target: HomeFragment
) = ViewModelProvider(target, factory).get(HomeViewModel::class.java)
}
}
#MustBeDocumented
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
Also, these are my app dependencies version:
kotlin_version = ‘1.3.72'
dagger_version = ‘2.27’
gradle:3.6.3
I know there are several questions with this problem but I tried several of them and none of them worked for me.
This are the solutions links that I tried reading and check:
https://github.com/android/architecture-components-samples/tree/master/GithubBrowserSample
https://github.com/google/dagger/issues/1478
Dagger/MissingBinding java.util.Map<java.lang.Class<? extends ViewModel>,Provider<ViewModel>> cannot be provided without an #Provides-annotated method
https://github.com/google/dagger/issues/1478
Error [Dagger/MissingBinding] androidx.lifecycle.ViewModelProvider.Factory cannot be provided without an #Provides-annotated method
https://medium.com/chili-labs/android-viewmodel-injection-with-dagger-f0061d3402ff
https://github.com/ChiliLabs/viewmodel-dagger-example
Try to use Architecture Blueprints sample (dagger-android branch) as an example.
Dagger Android is a mess itself and it's important to follow some template not to turn wrong way. May be your approach could be fixed as well, but I propose you to try change your schema:
You custom View Model factory should have #Inject in constructor:
class SaveMyHeroViewModelFactory #Inject constructor(
private val creators: #JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
.......
You should add auxiliary module ViewModelBuilderModule, that provides ViewModelProvider.Factory (that you inject in all your activities and fragments) with your custom ViewModel.Factory:
#Module
abstract class ViewModelBuilderModule {
#Binds
abstract fun bindViewModelFactory(factory: SaveMyHeroViewModelFactory): ViewModelProvider.Factory
}
For all your pairs - Activitiy/ViewModel and Fragment/ViewModel you should add Module like this (but you can make single module for all of them, it's up to you):
#Module
abstract class HomeModule {
#ContributesAndroidInjector(modules = [ViewModelBuilderModule::class])
internal abstract fun bind(): HomeFragment
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
internal abstract fun provideHomeViewModel(viewModel: HomeViewModel): ViewModel
}
In your Dagger component you should use all modules from step 3:
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class, HomeModule::class, ...])
interface SaveMyHeroApplicationComponent : AndroidInjector<SaveMyHeroApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): SaveMyHeroApplicationComponent
}
}
I have followed this tutorial in order to do DI in my viewmodels. But I currently am stuck.
I have created a ViewModelFactory for my viewmodel which is as follows:
class HomeViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>,
Provider<ViewModel>>
): ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return creators[modelClass]?.get() as T
}
}
Followed by a ViewModel:
class HomeViewModel #Inject constructor(private val songsRepository: SongsRepository): ViewModel()
For DI I have created two components. One is my main application component, the other is a component which depends on the main application.
#Singleton
#Component(modules = [AppModule::class])
public interface AppComponent {
fun songRepository(): SongsRepository
fun libraryManager(): LibraryManager
fun inject(mainActivity: MainActivity)
}
#Module
public class AppModule(val application: Application){
#Provides #Singleton
fun providesApplication(): Application{
return application
}
#Provides #Singleton
fun providesLibraryManager(): LibraryManager {
return LibraryManager(application)
}
#Provides #Singleton
fun providesSongRepository(libraryManager: LibraryManager): SongsRepository {
return SongsRepository(libraryManager)
}
}
And my ViewModelModule is as follows:
#Module
public class ViewModelModule {
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
#AppScope
#Provides
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
#AppScope
#IntoMap
#Provides
#ViewModelKey(HomeViewModel::class)
fun providesHomeViewModel(songsRepository: SongsRepository): HomeViewModel{
return HomeViewModel(songsRepository)
}
}
#AppScope
#Component(modules = [ViewModelModule::class], dependencies = [AppComponent::class])
public interface ViewModelComponent {
fun homeViewModelFactory(): HomeViewModelFactory
fun homeViewModel(): HomeViewModel
fun inject(homeFragment: HomeFragment)
}
The error I'm getting is this:
error: [Dagger/MissingBinding] java.util.Map,? extends
javax.inject.Provider> cannot be
provided without an #Provides-annotated method.
I seriously don't have any idea why is this happening since all my classes have #Inject constructors. Dagger's documentation is not helping me either. I would be grateful if you could advise me on this matter.
The error message indicates that the following code is wrong:
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
It should be
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
It is because the signature of Map interface is Map<K, out V>, that means the Map<..., Provider<ViewModel>> will be compiled to Map<..., ? extends Provider<ViewModel>> Java code, so you are asking dagger for latter one but it only has former one in its object graph, then the compiler throws you the error.
I have gone through all the answers with the above title but couldn't find the solution. Basically I want to do scoping. I want to inject ApiService only to HomeViewModel. It should not be available to LoginViewModel. I have my following setup and the error which I am getting:
Info: If I remove the provideLoginActivity() from ActivityModule everything works fine. Why behaving like that?
AppComponent:
#Singleton
#Component(
modules = [AndroidInjectionModule::class, ActivityModule::class, AppModule::class]
)
interface AppComponent : AndroidInjector<BaseApplication> {
#Component.Factory
interface Factory {
fun application(#BindsInstance baseApplication: BaseApplication): AppComponent
}
}
AppModule:
#Module
object AppModule {
#Singleton
#JvmStatic
#Provides
fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
ActivityModule:
#Module
abstract class ActivityModule {
#ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class])
internal abstract fun getHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ViewModelBuilder::class])
internal abstract fun provideLoginActivity(): LoginActivity
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
NetworkModule:
#Module
object NetworkModule {
#JvmStatic
#Provides
fun getApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
ViewModelFactory:
class ViewModelFactory #Inject constructor(
private val creators: #JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class: $modelClass")
}
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
#Module
internal abstract class ViewModelBuilder {
#Binds
internal abstract fun bindViewModelFactory(
factory: ViewModelFactory
): ViewModelProvider.Factory
}
#Target(
AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
HomeViewModel:
class HomeViewModel #Inject constructor(private val apiService: ApiService) : ViewModel() {
val todoLiveData: LiveData<Todo> = liveData(Dispatchers.IO) {
val response: Todo = apiService.getTodo(1)
emit(response)
}
}
Error:
error: [Dagger/MissingBinding] com.sagar.daggertest.repository.network.ApiService cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sagar.daggertest.BaseApplication> {
^
A binding with matching key exists in component: com.sagar.daggertest.di.HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent
com.sagar.daggertest.repository.network.ApiService is injected at
com.sagar.daggertest.HomeViewModel(apiService)
com.sagar.daggertest.HomeViewModel is injected at
com.sagar.daggertest.di.HomeActivityModule.bindHomeViewModel(homeViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sagar.daggertest.di.ViewModelFactory(creators)
com.sagar.daggertest.di.ViewModelFactory is injected at
com.sagar.daggertest.di.ViewModelBuilder.bindViewModelFactory$app_debug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sagar.daggertest.LoginActivity.viewModelFactory
com.sagar.daggertest.LoginActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sagar.daggertest.di.AppComponent → com.sagar.daggertest.di.HomeActivityModule_ProvideLoginActivity$app_debug.LoginActivitySubcomponent]
#ContributesAndroidInjector creates a subcomponent under the hood ( which is HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent in logs ).
In your ActivityModule, you're trying to provide HomeViewModel for map in ViewModelFactory, which is also injected at LoginActivity. But due to HomeViewModel needing ApiService and your ApiService is in NetworkModule which is scoped to subcomponent dagger generated - it fails.
Solution would be moving your multibinding to corresponding scope. By doing so, you're taking HomeViewModel out of map which is injected in LoginActivity, so it won't complain.
You can create a new module, let's say ViewModelModule and put your provider there:
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
and pass it along with other modules to HomeActivity's contributor:
#ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class, ViewModelModule::class])
internal abstract fun getHomeActivity(): HomeActivity
I'm using Android Architecture Component with Dagger2 which recommended by Google. I followed the Google Sample but it is not perfect.
This solution does simplify injection, but if my ViewModels rely on the database, a Dao was needed when create the viewmodel. For now the factory was binded to the AppModule for providing global using. So ViewModel must be provided in AppModule to adding to the map. Thus the Dao alse be global.
Since Dao may only used in some special activity, creating him globally is a waste and inconvenient to manage.
I tried to move definition of bindViewModel function to ActivityModule but errors was given as follwed:
[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
com.chenhe.platform.viewmodel.MyViewModelFactory(creators)
com.chenhe.platform.viewmodel.MyViewModelFactory is injected at
......
AppModule:
#Module(subcomponents = [LocalWatchFaceComponent::class], includes = [ViewModelModule::class])
class AppModule(private val context: Context) {
#Provides
#Singleton
fun provideContext(): Context = context
#Provides
#Singleton
fun provideAppDataBase(): AppDatabase = AppDatabase.getInstance(context)
// I want move code below to ActivityModule. But Dao was needed by viewmodel by factory map
#Provides
fun provideLocalWatchFaceDao(database: AppDatabase) = database.localWatchFaceDao()
}
ViewModel:
class LocalWatchFaceViewModel #Inject constructor(
private val appCtx: Context,
localWatchFaceRepository: LocalWatchFaceRepository) : ViewModel() {}
MapKey:
#MustBeDocumented
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
ViewModelModule:
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(LocalWatchFaceViewModel::class)
abstract fun bindLocalWatchFaceViewModel(viewModel: LocalWatchFaceViewModel): ViewModel
}
Factory:
#Singleton
class MyViewModelFactory #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.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Is there any plan to gracefully divide the scope?