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>()
}
All I want is set HomeViewModel
as an abstract class and inject it where I want.
The solution is to create a middle abstract class between the child and the real parent, then the child has to extend from that middle abstract class.
HomeViewModel & the Middle Abstract class:
open 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 class Implementation : HomeViewModel() {
abstract fun fetchAlbums()
}
}
HomeViewModelImpl:
class HomeViewModelImpl : HomeViewModel.Implementation() {
override fun fetchAlbums() { }
}
HomeFragment:
class HomeFragment : BaseFragment() {
@Inject
lateinit var viewModel: HomeViewModel
}
Source: https://stackoverflow.com/a/18331547/421467
If you're trying to inject a child of an abstract base class, you need to let dagger know how to create that instance. That's done via a method on a module that returns an instance of the type and has an @Provides annotation. That class will be called every time it needs to create an instance of the class (if you only wish 1 instance of it, you can also annotate it with a scope annotation like @Singleton).
The reason this is necessary is because the class you're trying to make is abstract. It can't be instantiated directly, so Dagger can't do its normal thing of calling the @Inject constructor or default constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With