21

I am trying to bind subclasses of ViewModel into a map by their KClass types:

@Module abstract class ViewModelModule {

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

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

}

But I am getting Dagger compiler error:

e: ~/Example/app/build/tmp/kapt3/stubs/debug/com/example/app/injection/AppComponent.java:5: error: [dagger.android.AndroidInjector.inject(T)] java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
e: 

e: public abstract interface AppComponent {
e:                 ^
e:       java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
e:           com.example.app.ui.ViewModelFactory.<init>(creators)
e:       com.example.app.ui.ViewModelFactory is injected at
e:           com.example.app.injection.ViewModelModule.bindViewModelFactory(p0)
e:       android.arch.lifecycle.ViewModelProvider.Factory is injected at
e:           com.example.app.ui.MyFragment.setViewModelFactory(p0)
e:       com.example.app.ui.MyFragment is injected at
e:           dagger.android.AndroidInjector.inject(arg0)

The above ViewModelModule is included in my AppModule, which is a module in my AppComponent. So Dagger should be able to provide the Map<KClass<out ViewModel>, Provider<ViewModel>> required by my ViewModelFactory, but I cannot figure out why it is crashing.


I also tried switching the ViewModelKey annotation class over to Java, taking a Class as a constructor parameter instead of a KClass. Then modified my ViewModelFactory to depend on a Map<Class<out ViewModel>, Provider<ViewModel>>, but the same error occurred.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Bryan
  • 14,756
  • 10
  • 70
  • 125

1 Answers1

40

When using KClass in an annotation, it actually gets compiled to Java's Class. But the actual issue is the wildcard in java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> that the Kotlin compiler is generating.

Assuming that @ViewModelKey is defined as

@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

You'll need to define your injection site as

Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>

Using @JvmSuppressWildcards will prevent the compiler from generating wildcards.

I don't actually know, why wildcards are not supported by the Dagger compiler. You can see a similar issue here: Dagger 2: How to inject Map<Class<? extends Foo>, Provider<? extends Foo>>

Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
  • Amazing. I would have never gotten to this, all the Google Android Architecture projects are in Java ! – Arjun Dec 13 '17 at 04:26
  • I would upvote 10 times if I could. Thank you so much. – John Michael Pirie Apr 12 '18 at 13:00
  • Does that mean that you cannot inject `KClass`, only `Class` into Kotlin code? – andras Nov 21 '18 at 07:49
  • @andras You can, however neither of those are injected here. – Kirill Rakhman Nov 21 '18 at 12:37
  • @KirillRakhman You said `When using KClass in an annotation, it actually gets compiled to Java's Class`. I think that's true because I did a couple tests. Dagger2 always threw an error when I tried to inject `KClass`, independent whether the annotation's value was `KClass` (like in your `ViewModelKey` example) or just `Class`. In every linked example there is always a `Class` injected. I wish I could see the actual dependency graph in Dagger2 for debugging, but there is no way for that, not even when using `kapt` instead `annotationProcessor`. (TLDR: But how?) – andras Nov 23 '18 at 05:04