Search code examples
mvvmkotlindependency-injectionviewmodeldagger-2

Dagger can not inject ViewModel with KClass


I am moving my project java to kotlin, but got some confusion about KClass and Class

Here is my BaseActivity

abstract class BaseActivity<DB : ViewDataBinding, VM : BaseViewModel> : DaggerAppCompatActivity() {

    private lateinit var mCustomDialog: CustomDialog
    private lateinit var mViewDataBinding: DB
    private lateinit var mViewModel : VM

    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Set Custom Dialog
        mCustomDialog = CustomDialog(this, R.style.LoadingDialogStyle)
        // Set ViewModel
        mViewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)
        // Set DataBinding
        mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId())
        mViewDataBinding.lifecycleOwner = this
        mViewDataBinding.setVariable(getBindingVariable(), mViewModel)
        mViewDataBinding.executePendingBindings()
        // Initialize UI
        prepareView(savedInstanceState)
    }


    @LayoutRes
    abstract fun getLayoutId(): Int


    protected abstract fun getViewModelClass(): KClass<VM>


    abstract fun getBindingVariable(): Int


    fun getViewModel(): VM {
        return mViewModel
    }


    fun getViewDataBinding() : DB {
        return mViewDataBinding
    }

I am using protected abstract fun getViewModelClass(): KClass<VM> function for initializing ViewModel class in the function below

ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass().java)

I use ViewModel in activities by this way

class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {

    override fun getViewModelClass(): KClass<SplashViewModel> {
        return SplashViewModel::class
    }

    override fun getLayoutId(): Int {
        return R.layout.activity_splash
    }

    override fun getBindingVariable(): Int {
        return BR.vm
    }

    override fun prepareView(savedInstanceState: Bundle?) {
        getViewModel().testLog()
    }

}

But when I run the project, I got this error

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.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.example.MyApp> {
                ^
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.example.example.utils.ViewModelFactory(viewModels)
      com.example.example.utils.ViewModelFactory is injected at
          com.example.example.base.BaseActivity.viewModelFactory
      com.example.example.ui.splash.SplashActivity is injected at
          dagger.android.AndroidInjector.inject(T) [com.example.example.di.AppComponent ? com.example.example.di.ActivityBindingsModule_SplashActivityInjector$app_debug.SplashActivitySubcomponent]

So I made some research and find out it is about KClass in my ViewModelKey

Here is ViewModelKey

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

If I do not change my code to Kotlin and use old Java class like this it works properly

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
    Class<? extends ViewModel> value();
}

This is my ViewModelFactory class

@Suppress("UNCHECKED_CAST")
class ViewModelFactory @Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = viewModels[modelClass]
            ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
            ?: throw IllegalArgumentException("unknown model class $modelClass")
        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

My SplashActivityModule

@Module
abstract class SplashActivityModule {

    @Binds
    @IntoMap
    @ViewModelKey(SplashViewModel::class)
    internal abstract fun provideSplashViewModel(splashViewModel: SplashViewModel) : ViewModel

}

So how can I use ViewModelKey properly with Kotlin and what is main cause of this error, any help will be appreciated


Solution

  • As mentioned this question problem is related to Kotlin version. Using higher than 1.3.30 version solves the problem.