Ошибка компиляции Dagger2 в MVVM Factory при определении инжектора для фрагмента - PullRequest
0 голосов
/ 19 ноября 2018

У меня есть следующий корневой компонент:

@Singleton
@Component(modules = [AndroidInjectionModule::class,
        AndroidSupportInjectionModule::class, 
        ActivityBuilderModule::class])
interface RootComponent : AndroidInjector<DaggerApplication> {
    fun inject(myApplication: MyApplication)
    override fun inject(photoPartyApplication: DaggerApplication)

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder
        fun build(): RootComponent
    }
}

В ActivityBuilderModule:

@Module
abstract class ActivityBuilderModule {

    @ContributesAndroidInjector(modules = [MainActivityModule::class,
            ViewModelModule::class])
    @ActivityScope
    abstract fun bindMainActivity(): MainActivity

    @ContributesAndroidInjector(modules = [SecondaryActivityModule::class,
            ViewModelModule::class,
            FragmentBuilderModule::class])
    @ActivityScope
    abstract fun bindSecondaryActivity(): SecondaryActivity

}

ViewModelModule - это тривиальный модуль, помогающий создавать конструкторы в классах ViewModel, и состоит из @Binds между конкретными экземплярами и типом ViewModel.

MainActivityModule и SecondaryActivityModule определяют конкретные зависимости для соответствующих действий.

Ключевым моментом является то, что когда я добавил этот FragmentBuilderModule - компиляция начала выдавать ошибки. Трассировка стека следующая:

ошибка: [Dagger / MissingBinding] some_package.SpecificDependency не может быть предоставлен без конструктора @Inject или метода, аннотированного @ Provides.

открытый абстрактный интерфейс RootComponent расширяет dagger.android.AndroidInjector {

^

В компоненте существует привязка с соответствующим ключом: some_package.ActivityBuilderModule_BindMainActivity.MainActivitySubcomponent

some_package.SpecificDependency добавляется в some_package.MainViewModel (specificDependency,…)

some_package.MainViewModel внедряется в some_package.ViewModelModule.mainViewModel (viewModel)

Map<Class<? extends ViewModel>, Provider<ViewModel>> is injected at
      some_package.ViewModelFactory(viewModelProviders)

some_package.ViewModelFactory вставляется в some_package.ViewModelModule.bindViewModelFactory (factory)

android.arch.lifecycle.ViewModelProvider.Factory вставляется в some_package.MyFragment.viewModelFactory

some_package.MyFragment внедряется в dagger.android.AndroidInjector.inject (T)

[some_package.RootComponent → some_package.ActivityBuilderModule_BindSecondaryActivity.SecondaryActivitySubcomponent → some_package.FragmentBuilderModule_ProvideMyFragmentFactoryMyFragmentSubcomponent]

Насколько я понимаю, Даггер предполагает, что весь граф зависимостей должен быть правильно построен для map из Class<? extends ViewModel> -> Provider<ViewModel>, а если некоторые ViewModels попадают в factory, и что factory внедренный в компонент, тогда, если компонент запросит любой viewmodel, , он должен быть доставлен . И чтобы доставить все viewmodels, опять же, все зависимости должны быть доступны (что не так, потому что конкретная зависимость для MainViewModel доступна только от MainModule, и это то, что кинжал говорит перед трассировкой стека) ).

Есть ли обходной путь для предоставления зависимостей для map из Class<? extends ViewModel> -> Provider<ViewModel> по требованию вместо построения всего графика во время компиляции (что приводит к ошибке во время компиляции)

1 Ответ

0 голосов
/ 20 декабря 2018

Сначала добавьте ViewModelModule в RootComponent

 @Singleton
 @Component(modules = [AndroidInjectionModule::class,
            AndroidSupportInjectionModule::class, 
            ActivityBuilderModule::class,
            ViewModelModule::class])                 // add this so you don't have to add for every activity
    interface RootComponent : AndroidInjector<DaggerApplication> {
        fun inject(myApplication: MyApplication)
        override fun inject(photoPartyApplication: DaggerApplication)

        @Component.Builder
        interface Builder {
            @BindsInstance
            fun application(application: Application): Builder
            fun build(): RootComponent
        }
    }

Теперь в ActivityBuilderModule добавьте только activty

 @Module
 abstract class ActivityBuilderModule {

    @ContributesAndroidInjector
    @ActivityScope
    abstract fun bindMainActivity(): MainActivity
 }

Теперь в ViewModelModuleДобавить все ViewModel

 @Module
 abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainActivityModule::class)
    abstract fun bindMainActivityViewModel(mainActivityViewModel: MainActivityModule): ViewModel
}

Добавить ViewModelKey

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

И ViewModelFactory

@Singleton
class KotlinViewModelFactory @Inject
constructor(private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    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 {
            Timber.d(creator.toString())
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
...