3

I've tried, to create several components. First will store main parts of app, like ViewModel's Fabric, Context, other settings. Other components -- component per screen. So, f.ex. I have FirstScreen. I've tried to create component with its ViewModel:

@Subcomponent(modules = [StoreModule::class])
@StoreScope
interface StoreComponent {

    fun inject(activity: MainActivity)

    fun inject(fragment: StoreFragment)

    @Subcomponent.Builder
    interface Builder {

        fun build(): StoreComponent

    }

}

So, StoreViewModel, as its dependency, StoreRepository, builds in module of StoreComponent:

@Module
abstract class StoreModule {

    @Binds
    @IntoMap
    @ViewModelKey(StoreViewModelImpl::class)
    abstract fun getStoreViewModel(viewModel: StoreViewModelImpl): ViewModel

    @Binds
    @StoreScope
    abstract fun getStoreRepository(repository: StoreRepositoryImpl): StoreRepository

}

And this is AppComponent:

@Component(modules = [
    GsonModule::class,
    RetrofitModule::class,
    ViewModelsFactoryModule::class,
    CiceroneModule::class
])
@Singleton
interface AppComponent {

    fun getStoreComponentBuilder(): StoreComponent.Builder

}

Here buildes ViewModel's Factory:

@Module
abstract class ViewModelsFactoryModule {

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


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

}

But, when I try to build project, I have en error, that dagger can't provide Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel> into ViewModelFactory-class without @Provides-annonated method.

error: [com.sagrishin.smartreader.di.components.StoreComponent.inject(com.sagrishin.smartreader.presentation.fragments.StoreFragment)] 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.
public abstract interface AppComponent {
                ^
      java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
          com.sagrishin.smartreader.presentation.viewmodels.factory.ViewModelFactory.<init>(creators)
      com.sagrishin.smartreader.presentation.viewmodels.factory.ViewModelFactory is injected at
          com.sagrishin.smartreader.presentation.fragments.StoreFragment.viewModelsFactory
      com.sagrishin.smartreader.presentation.fragments.StoreFragment is injected at
          com.sagrishin.smartreader.di.components.StoreComponent.inject(fragment)

UPD: viewmodel factory:

typealias ViewModelsProvidersMap =
        Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>


@Singleton
class ViewModelFactory : ViewModelProvider.Factory {

    private val creators: ViewModelsProvidersMap

    @Inject
    constructor(creators: ViewModelsProvidersMap) {
        this.creators = creators
    }

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

    }
}
Sergey Grishin
  • 392
  • 2
  • 16
  • 1
    Hello! I think this is a good question—you've done research and provided a lot of code. Per [this meta discussion](https://meta.stackoverflow.com/q/303812/1426891), can you please copy and paste your code to make it more searchable and accessible instead of including it in images/screenshots? This would also help answerers copy/paste your example so they can diagnose your problem and test their answers. Thank you! – Jeff Bowman Dec 11 '18 at 02:09
  • also provide your ViewModelFactory class – Shweta Chauhan Dec 11 '18 at 07:08
  • @JeffBowman, I updated my question see it , please – Sergey Grishin Dec 13 '18 at 15:13
  • @ShwetaChauhan, I updated my question, see it, please – Sergey Grishin Dec 13 '18 at 15:16
  • @Sergey I don't know the answer; I upvoted shortly after the edit. I'm unfamiliar with Kotlin, and I have a hunch that Kotlin's type transformations are interfering with Dagger's multibindings here. The code otherwise looks pretty reasonable. – Jeff Bowman Dec 13 '18 at 15:53
  • 1
    @JeffBowman, all in all, thank you for spending time to my question. You made a good advice: try to rewrite this to Java. To be honest, I don't know, will this help, but try – Sergey Grishin Dec 13 '18 at 18:04
  • @SergeyGrishin : I want to suggest you to change in ViewModelFactory but I don't know that help you or not just want to try it. Change ViewModelFactory like this : https://gist.github.com/tinmegali/899aca5d240d3a92efd060f87831a32e – Shweta Chauhan Dec 14 '18 at 04:04
  • @ShwetaChauhan, thank you, I will try it and describe results – Sergey Grishin Dec 14 '18 at 05:40
  • i have same problem with kotlin 1.3.30 version. wh? downgrade to 1.3.21 it's work – denizs Apr 18 '19 at 12:02
  • Yes, the same issue happened to me in 1.3.30, and the downgrade prevented it – Juan Mendez May 22 '19 at 22:20
  • Please check at this https://stackoverflow.com/questions/55669810/dagger-missingbinding-java-util-mapjava-lang-class-extends-viewmodel-provide/62260147#62260147 – Daniele Jun 08 '20 at 10:52

4 Answers4

1

You don't have to switch to Java, and simply use kotlin 1.3.31 to have it fixed. https://github.com/google/dagger/issues/1478#issuecomment-486712176

include the following Jvm annotation

Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
Juan Mendez
  • 2,658
  • 1
  • 27
  • 23
0

I fixed the same issue I was having by switching the ViewModelFactory.kt and ViewModelsFactoryModule.kt to java.

ViewModelFactory.java

@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @NonNull
    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

ViewModelsFactoryModule.java

@Module
abstract class ViewModelModule {

    @NonNull
    @Binds
    @IntoMap
    @ViewModelKey(StoreViewModelImpl.class)
    abstract ViewModel bindLauncherViewModel(StoreViewModelImpl viewModel);

    @NonNull
    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory);
}
Aaron Thompson
  • 1,849
  • 1
  • 23
  • 31
0
No need to use @JvmSuppressWildcards. As the day of my Post my Kotlin version
 is : '1.3.40'

 Use dagger dependency in build.gradle

 apply plugin: 'kotlin-kapt'

 //Dagger dependencies
implementation 'com.google.dagger:dagger:2.22.1'
kapt 'com.google.dagger:dagger-compiler:2.22.1'

implementation 'com.google.dagger:dagger-android:2.22'
implementation 'com.google.dagger:dagger-android-support:2.22' // if you use the support libraries
kapt 'com.google.dagger:dagger-android-processor:2.22'


And for Java use the following dagger dependency

 //Dagger dependencies
implementation 'com.google.dagger:dagger:2.22.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'

implementation 'com.google.dagger:dagger-android:2.22'
implementation 'com.google.dagger:dagger-android-support:2.22' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.22'
yash786
  • 1,151
  • 8
  • 18
0

Your dependency version might be different, for instance your core/compiler versions are 2.22 or some other but your android versions are 2.16 this might be causing the exception as well, just do this:
from this

    implementation 'com.google.dagger:dagger:2.22'
    kapt 'com.google.dagger:dagger-compiler:2.22'
    // For android
    implementation 'com.google.dagger:dagger-android:2.16'
    implementation 'com.google.dagger:dagger-android-support:2.16'
    // if you use the support libraries
    kapt 'com.google.dagger:dagger-android-processor:2.16'

to this

 //Dagger 2
    implementation 'com.google.dagger:dagger:2.22'
    kapt 'com.google.dagger:dagger-compiler:2.22'
    // For android
    implementation 'com.google.dagger:dagger-android:2.22'
    implementation 'com.google.dagger:dagger-android-support:2.22'
    // if you use the support libraries
    kapt 'com.google.dagger:dagger-android-processor:2.22'
USMAN osman
  • 952
  • 7
  • 13