Класс не может быть предоставлен без аннотированного метода @ Provides - PullRequest
0 голосов
/ 21 сентября 2018

Я хочу добавить зависимость ( HomeViewModel ) в мой фрагмент ( HomeFragment ).

У меня есть класс ( HomeViewModelImpl ), который реализовал эту абстракцию ( HomeViewModel ), и внутри этого класса я переопределяю методы родителя, конечно.

Класс абстракции ( HomeViewModel ) является абстрактным классом, который расширен от BaseViewModel .

BaseViewModel - это обычный класс open, расширенный с ViewModel класса из компонента жизненного цикла Android .

Проблема в том, что у меня возникает ошибка, когда я хочу вставить 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 как абстрактный класс и внедрить его туда, куда я хочу.

Ответы [ 3 ]

0 голосов
/ 21 сентября 2018

Это все здесь.

Кинжал не может создать экземпляр класса без @Inject constructor(...).С другой стороны, в Java / Kotlin нельзя создать экземпляр абстрактного класса, а Dagger"использует" Java / Kotlin в этом случае.

У вас есть возможность расширить HomeViewModel и добавить дочерний экземпляр с помощью Dagger или сделать HomeViewModel не абстрактным.

0 голосов
/ 22 сентября 2018

Решение состоит в том, чтобы создать средний абстрактный класс между дочерним и реальным родительским классом, а затем дочерний объект должен расширяться от этого среднего абстрактного класса.

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
}

Источник: https://stackoverflow.com/a/18331547/421467

0 голосов
/ 21 сентября 2018

Если вы пытаетесь внедрить дочерний элемент абстрактного базового класса, вам нужно сообщить кинжалу, как создать этот экземпляр.Это делается с помощью метода в модуле, который возвращает экземпляр типа и имеет аннотацию @Provides.Этот класс будет вызываться каждый раз, когда ему нужно создать экземпляр класса (если вы хотите только один его экземпляр, вы также можете аннотировать его с помощью аннотации области видимости, например @Singleton).

Причина в том, чтонеобходимо, потому что класс, который вы пытаетесь сделать, является абстрактным.Его нельзя создать напрямую, поэтому Dagger не может нормально вызывать конструктор @Inject или конструктор по умолчанию.

...