我想将依赖项( HomeViewModel )插入片段( HomeFragment )中。
我有一个实现该抽象( HomeViewModel )的类( HomeViewModelImpl ),在该类内部,我当然要覆盖父级的方法。
抽象类( HomeViewModel )是从 BaseViewModel 扩展的抽象类。
BaseViewModel 是普通的open
类,其中是 Android 生命周期组件的 ViewModel 类的扩展。
问题是我想向片段中插入HomeViewModel
时出错:
> 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>()
}
我所需要的全部都设为HomeViewModel
作为抽象类,并将其注入到我想要的位置。
答案 0 :(得分:2)
如果要注入抽象基类的子代,则需要让dagger知道如何创建该实例。这是通过模块上的方法完成的,该方法返回该类型的实例并具有@Provides批注。每次需要创建该类的实例时都会调用该类(如果您只希望一个实例,则还可以使用@Singleton之类的作用域注释对其进行注释。)
之所以这样做是必要的,是因为您要创建的类是抽象的。它不能直接实例化,因此Dagger无法完成其正常工作,即调用@Inject构造函数或默认构造函数。
答案 1 :(得分:2)
解决方案是在子级和真正的父级之间创建一个中间抽象类,然后该子类必须从该中间抽象类扩展。
HomeViewModel 和中间抽象类:
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
}
答案 2 :(得分:1)
都在这里。
Dagger 在没有@Inject constructor(...)
的情况下无法创建类的实例。另一方面,在 Java / Kotlin 中,您无法创建抽象类的实例,并且 Dagger “使用” Java / 科特林。
您可以选择扩展HomeViewModel
并使用 Dagger 注入子实例,或者使HomeViewModel
不抽象。