Felipezad
Felipezad

Reputation: 35

Dagger2 Missing Binding Cannot Provides ViewModel Key

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

Upvotes: 0

Views: 1918

Answers (1)

sergiy tykhonov
sergiy tykhonov

Reputation: 5103

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:

  1. 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 {
.......
  1. 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
}
  1. 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
}
  1. 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
    }
}

Upvotes: 2

Related Questions