4

I'm trying to use dagger 2 on my Android application to inject the new ViewModel from arch android library.

From what I see on this sample https://github.com/googlesamples/android-architecture-components/tree/e33782ba54ebe87f7e21e03542230695bc893818/GithubBrowserSample I need to use this:

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(LoginViewModel::class)
    internal abstract fun bindLoginViewModel(viewModel: LoginViewModel): LoginViewModel

    @Binds
    @IntoMap
    @ViewModelKey(MainMenuViewModel::class)
    internal abstract fun bindSearchViewModel(viewModel: MainMenuViewModel): MainMenuViewModel

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

@ApplicationScope
@Component(modules = arrayOf(ApplicationModule::class, NetworkModule::class, ViewModelModule::class))
interface ApplicationComponent {
    fun plusActivityComponent(activityModule: ActivityModule): ActivityComponent
    fun inject(application: LISAApplication)

}

And my factory is:

@ApplicationScope
class ViewModelFactory @Inject constructor(private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    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 {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

    }
}

But the project doesn't compile :( I have this error (Map<...> cannot be provided without an @Provides-annotated method.):

Using Kotlin incremental compilation
:mobile:transformDataBindingWithDataBindingMergeArtifactsForDebug UP-TO-DATE
:mobile:kaptDebugKotlin
e: /Users/jaumard/LISAProjects/LISA/mobile/build/tmp/kapt3/stubs/debug/com/mylisabox/lisa/dagger/components/ApplicationComponent.java:6: error: [com.mylisabox.lisa.dagger.components.ActivityComponent.inject(com.mylisabox.lisa.common.BaseActivity)] 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.
e: 

e: public abstract interface ApplicationComponent {
e:                 ^
e:       java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
e:           com.mylisabox.lisa.dagger.ViewModelFactory.<init>(creators)
e:       com.mylisabox.lisa.dagger.ViewModelFactory is injected at
e:           com.mylisabox.lisa.common.BaseActivity.factory
e:       com.mylisabox.lisa.common.BaseActivity is injected at
e:           com.mylisabox.lisa.dagger.components.ActivityComponent.inject(activity)
e: /Users/jaumard/LISAProjects/LISA/mobile/build/tmp/kapt3/stubs/debug/com/mylisabox/lisa/dagger/components/ActivityComponent.java:4: error: com.mylisabox.lisa.dagger.components.ActivityComponent scoped with @com.mylisabox.network.dagger.annotations.ActivityScope may not reference bindings with different scopes:
e: 

Any idea on how fix this ?

jaumard
  • 8,202
  • 3
  • 40
  • 63
  • Had a similar issue using `@Binds` annotation with Kotlin - I had to change to return a concrete implementation with `@Provides` rather than an abstract method. Would be good if someone can offer a solution. – Mark Sep 26 '17 at 20:45
  • Hello Mark, so you mean you remove all abstract methods and do @Provides instead ? Witch mean that the Map has to be build manually right ? – jaumard Sep 27 '17 at 05:20
  • Find the answer for me, check it, hope it will work for you too – jaumard Sep 27 '17 at 15:58
  • Mine was slightly different, my abstract method parameter was the interface implementing class, and the return type was the interface type - Although I might have had it backwards, so I'll check - cheers for the update with your solution. – Mark Sep 27 '17 at 16:22

1 Answers1

9

So ok I found the issue, the problem was under my ViewModelModule I need to return ViewModel from my abstract methods not directly the type I want. It will become like this then :

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(LoginViewModel::class)
    internal abstract fun bindLoginViewModel(viewModel: LoginViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(MainMenuViewModel::class)
    internal abstract fun bindSearchViewModel(viewModel: MainMenuViewModel): ViewModel

    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
jaumard
  • 8,202
  • 3
  • 40
  • 63
  • 7
    I had done this but it still failed. i had to annotate `Provider>` with `@JvmSuppressWildcards` annotation for it to work. `class ViewModelFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory {` – Edijae Crusar Dec 28 '17 at 12:47
  • 1
    You're right, @JvmSuppressWildcards is mandatory, I didn't put it in my answer because it was already present on my factory code of my question – jaumard Jan 03 '18 at 10:58
  • Hey @jaumard I am getting the same error.. Tried the exact answer not able to solve still.. any other way? – Vrushali Raut Sep 10 '18 at 16:50
  • Hum not as I'm aware of sorry, I guess lib have evolved since this so it might need more changes – jaumard Sep 11 '18 at 06:48
  • I tried removing @Singleton annotation and it worked. – glisu Oct 04 '18 at 03:59
  • 1
    In the end adding the `@JvmSuppressWildcards` wasn't enough, it was still failing for Kotlin v1.3.31 The solution is here: https://youtrack.jetbrains.com/issue/KT-30979 just add `kapt { correctErrorTypes = true}` to your `app/build.gradle` on top of `dependencies` section – ExpensiveBelly Apr 27 '19 at 10:07
  • For others refrence this issue is solved in kotlin version 1.3.31 : https://github.com/JetBrains/kotlin/blob/1.3.30/ChangeLog.md – Mohammed Rampurawala May 18 '19 at 03:16