I want to inject a dependency (HomeViewModel) into my fragment (HomeFragment).
I have a class (HomeViewModelImpl) which implemented that abstraction (HomeViewModel) and inside this class, I'm overriding parent's methods of course.
The abstraction class (HomeViewModel) is an abstract class which extended from BaseViewModel.
The BaseViewModel is a normal open
class in which extended from ViewModel class from the Android lifecycle component.
The problem is I got an error when I want to inject HomeViewModel
into the fragment:
> error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] com.example.mvvm.ui.home.HomeViewModel cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.example.mvvm.MyApplication> {
^
com.example.mvvm.ui.home.HomeViewModel is injected at
com.example.mvvm.ui.home.HomeFragment.viewModel
com.example.mvvm.ui.home.HomeFragment is injected at
dagger.android.AndroidInjector.inject(T)
HomeFragment:
class HomeFragment : BaseFragment() {
//Error comes from this line
@Inject
lateinit var viewModel: HomeViewModel
}
HomeViewModel:
//If I write @Inject annotation here, the error goes away,
//but then I have to remove the abstract keyword, then I have to open the class
//and the useful usage of that abstract class in HomeViewModelImpl class
//will be gone, and I have to set open keyword on the HomeViewModel and
//on its method.
/*open*/ abstract class HomeViewModel /*@Inject constructor()*/ : BaseViewModel() {
sealed class State {
data class AlbumsLoaded(val albums: List<AlbumData>) : State()
object ShowLoading : State()
object ShowContent : State()
object ShowError : State()
}
abstract fun fetchAlbums()
}
BaseViewModel:
open class BaseViewModel : ViewModel() {
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
protected fun addDisposable(disposable: Disposable) {
compositeDisposable.add(disposable)
}
private fun clearDisposables() {
compositeDisposable.clear()
}
override fun onCleared() {
clearDisposables()
}
}
HomeModule:
@Module(includes = [
//HomeModule.HomeViewModelProvide::class,
HomeModule.HomeVM::class])
internal abstract class HomeModule {
@ContributesAndroidInjector
internal abstract fun homeFragment(): HomeFragment
@Module
abstract class HomeVM {
@Binds
@IntoMap
@ViewModelKey(HomeViewModelImpl::class)
internal abstract fun bindHomeViewModel(viewModel: HomeViewModelImpl): HomeViewModel
//I've changed the return type of this method from HomeViewModel to
//BaseViewModel and ViewModel, but error still exists!
}
//I've written this to provide HomeViewModel, but compiler shows another error
//that says there is a dependency circle!
/*@Module
class HomeViewModelProvide {
@Provides
internal fun provideHomeViewModel(homeViewModel: HomeViewModel): HomeViewModel = homeViewModel
}*/
}
ViewModelKey:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
ViewModelFactory:
class ViewModelFactory @Inject constructor(
private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
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 {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
ViewModelModule:
@Module
internal abstract class ViewModelModule {
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
BaseModule:
@Module
internal abstract class BaseModule {
@ContributesAndroidInjector(modules = [HomeModule::class])
internal abstract fun mainActivity(): MainActivity
}
AppComponent:
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
ViewModelModule::class,
AppModule::class,
BaseModule::class
])
interface AppComponent : AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>()
}
Note: Please read inline comments on above snippet codes.
All I want is set HomeViewModel
as an abstract class and inject it where I want.