8

This is how I'm trying to provide my ViewModelFactory:

@Suppress("UNCHECKED_CAST")
@Singleton
class ViewModelFactory @Inject constructor(
    private val viewModels: MutableMap<Class<out ViewModel>, 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
annotation class ViewModelKey(val value: KClass<out ViewModel>)

And this is how I'm binding the ViewModelFactory:

@Suppress("unused")
@Module
abstract class ViewModelModule  {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
}

I'm receiving the following error during build:

di/Injector.java:9: 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.

From my Activity I'm trying to receive the ViewModelFactory this way:

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
Zoe
  • 27,060
  • 21
  • 118
  • 148
Luca Nicoletti
  • 2,265
  • 2
  • 18
  • 32

3 Answers3

18

I faced the same issue recently. Version Kotlin:1.3.40 Dagger:2.23.2 I tried following the solutions mentioned in this post and here

but none seemed to work. The annotation processor of Dagger is not playing well with KAPT and for the same reason, the builds are failing. This is also updated on Kotlin issue too.

For me, converting both the ViewModelKey and ViewModelFactory to java worked. For Dagger, the tracking issue can be found here.

Update Adding @JvmSuppressWildcards fixed the issue. Code looks like this:

private val providers: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
reactivedroid
  • 752
  • 1
  • 7
  • 14
8

After digging a little bit more I found the issue. It's completely un-related to the code I'm using. It regards Kotlin 1.3.30.

Here some more information about it.

Downgrading to Kotlin 1.3.21 resolved the problem.

Luca Nicoletti
  • 2,265
  • 2
  • 18
  • 32
  • According to [the GitHub issue](https://github.com/google/dagger/issues/1478), user [bleeding182](https://github.com/bleeding182) suggested [a workaround](https://github.com/google/dagger/issues/1478#issuecomment-484825244) to instead refactor `ViewModelKey` to Java. It works for me. – Tanin Apr 22 '19 at 07:41
  • Hi, please check the solution below. – Daniele Jun 08 '20 at 10:38
-2

First of all ViewModelFactory shouldn't be a Singleton. Anyway to fix this you should have:

  • a ViewModelFactory

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

  • bind this factory in your module class

    @Binds abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

  • have a ViewModelKey

    @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) @MapKey annotation class ViewModelKey(val value: KClass<out ViewModel>)

  • bind your viewModel in the desired module

    @Binds @IntoMap @ViewModelKey(MyViewModel::class) abstract fun bindCustomViewModel(viewModel: MyViewModel): ViewModel

  • then implement and use your viewModel

    @Inject lateinit var viewModelFactory: ViewModelFactory

    private val viewModel: MyViewModel by viewModels { viewModelFactory }

This solution is tested with Kotlin 1.3.72 and latest version of Dagger2. Also please check at https://github.com/android/architecture-components-samples/tree/master/GithubBrowserSample

Hope this will help you.

Daniele
  • 1,030
  • 9
  • 20
  • Update: Now Hilt suppress this problem. Please use @ViewModelInject. Please refer to https://developer.android.com/training/dependency-injection/hilt-jetpack – Daniele Jun 18 '20 at 14:12