HILT: репозиторий свойств lateinit не инициализирован в ViewModel

Я столкнулся с этой проблемой в многомодульном проекте Android с HILT.

 kotlin.UninitializedPropertyAccessException: lateinit property repository has not been initialized in MyViewModel

Мои модули

  1. Модуль приложения
  2. Модуль Viewmodel
  3. Модуль UseCase
  4. Модуль DataSource

"Модуль приложения"

@AndroidEntryPoint
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
    return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
    viewModel.test()
}}

"Модуль ViewModel"

class MainViewModel @ViewModelInject constructor(private val repository: MyUsecase): ViewModel() {
fun test(){
    repository.test()
}}

Модуль UseCase

class MyUsecase @Inject constructor() {

@Inject
lateinit var feature: Feature

fun doThing() {
    feature.doThing()
}

@Module
@InstallIn(ApplicationComponent::class)
object FeatureModule {
    @Provides
    fun feature(realFeature: RealFeature): Feature = realFeature
}
}

"Модуль источника данных"

interface Feature {
fun doThing()
}

class RealFeature : Feature {
override fun doThing() {
    Log.v("Feature", "Doing the thing!")
}
}

Зависимости

MyFragment --- ›MyViewModel ---› MyUseCase --- ›Источник данных

что я сделал не так с этим кодом, пожалуйста, исправьте его.


person Arun    schedule 10.07.2020    source источник
comment
Вы пробовали использовать внедрение конструктора в сочетании с @ViewModelInject?   -  person EpicPandaForce    schedule 10.07.2020
comment
В частности, следует просто поместить переменную репозитория в свой конструктор. Я предполагаю, что @ViewModelInject поддерживает только внедрение конструктора.   -  person David Liu    schedule 10.07.2020
comment
@EpicPandaForce я пробовал. Это не работает. Иногда тип не может быть введен конструктором. Это может произойти по нескольким причинам. Например, вы не можете внедрить интерфейс с помощью конструктора. Вы также не можете внедрить конструктор типа, которым вы не владеете, например, класса из внешней библиотеки. В этих случаях вы можете предоставить Hilt информацию о привязке с помощью модулей Hilt. В этом единственном документе говорится   -  person Arun    schedule 10.07.2020
comment
Похоже, MyUsecase отсутствует @Inject constructor   -  person EpicPandaForce    schedule 10.07.2020
comment
Вы не можете конструктор внедрить интерфейс, потому что интерфейс в любом случае не может быть построен. В этом случае вы владеете фактической реализацией, которая может быть введена конструктором. Да, есть случаи, когда внедрение поля является единственным ответом, но на данный момент вашему примеру это не нужно, и вам следует перейти к внедрению конструктора.   -  person David Liu    schedule 11.07.2020


Ответы (5)


над классом активности вы должны добавить аннотацию @AndroidEntryPoint, как показано ниже:

@AndroidEntryPoint class MainActivity: AppCompatActivity () {

person Mosayeb Masoumi    schedule 09.05.2021

Проблема в коде в том, что @ViewModelInject не работает как @Inject в других классах. Вы не можете выполнить внедрение поля в ViewModel.

Ты должен сделать:

class MainViewModel @ViewModelInject constructor(
  private val myUseCase: MyUsecase
): ViewModel() {

  fun test(){
    myUseCase.test()
  }
}

Попробуйте использовать тот же шаблон для класса MyUsecase. Зависимости следует передавать в конструкторе, а не @Inject в теле класса. Этот вид побеждает цель внедрения зависимостей.

person manuelvicnt    schedule 10.07.2020
comment
Спасибо за ваш ответ. Теперь возникла другая проблема. Основываясь на моем примере, я не могу внедрить интерфейс Feature в класс MyUsecase. он говорит, что класс пространственных объектов не имеет доступа. - person Arun; 10.07.2020
comment
На этом этапе вам нужно просто загрузить что-то в github, поскольку это проблема с градиентом, а не проблема с кинжалом, и выходит за рамки исходного вопроса. - person David Liu; 13.07.2020

Помимо перемещения всего вашего материала во внедрение конструктора, ваш RealFeature не внедряется, потому что вы создаете его экземпляр вручную, а не позволяете Dagger построить его за вас. Обратите внимание, как ваш FeatureModule напрямую вызывает конструктор для RealFeature и возвращает его для метода @Provides. Dagger будет использовать этот объект как есть, поскольку думает, что вы выполнили для него всю настройку. Внедрение поля работает, только если вы позволите Кинжалу построить его.

Ваш FeatureModule должен выглядеть так:

@Module
@InstallIn(ApplicationComponent::class)
object FeatureModule {
    @Provides
    fun feature(realFeature: RealFeature): Feature = realFeature
}

Или с аннотацией @Binds:

@Module
@InstallIn(ApplicationComponent::class)
interface FeatureModule {
    @Binds
    fun feature(realFeature: RealFeature): Feature
}

Это также подчеркивает, почему вам следует перейти к внедрению конструктора; с внедрением конструктора эта ошибка была бы невозможна.

person David Liu    schedule 11.07.2020
comment
Обновил мой код. Теперь «нет доступа к классу RealFeature». Если я использую api вместо реализации, то все работает нормально. @manuelvicnt говорит, что HILT поддерживает только api для нескольких модулей. - person Arun; 11.07.2020
comment
@Arun здесь недостаточно, чтобы диагностировать проблему с градиентом. Для этого мне нужно увидеть весь проект. - person David Liu; 14.07.2020

Во-первых, я думаю, что вам не хватает @Inject в вашем классе RealFeature, поэтому Hilt знает, как внедрить зависимость. Во-вторых, если вы хотите внедрить в класс, который не является частью точек входа, поддерживаемых Hilt, вам необходимо определить свою собственную точку входа для этого класса.

В дополнение к модулю, который вы написали с помощью @Provides метода, вам нужно указать Hilt, как можно получить доступ к зависимости.

В вашем случае вы должны попробовать что-то вроде этого:

@EntryPoint
@InstallIn(ApplicationComponent::class)
interface FeatureInterface {
    fun getFeatureClass(): Feature
}

Затем, когда вы захотите его использовать, напишите что-нибудь вроде этого:

        val featureInterface =
        EntryPoints.get(appContext, FeatureInterface::class.java)
        val realFeature = featureInterface.getFeatureClass()

Вы можете найти больше информации здесь:

https://dagger.dev/hilt/entry-points

https://developer.android.com/training/dependency-injection/hilt-android#not-supported

person Samir Spahic    schedule 29.07.2020

class MainViewModel @ViewModelInject constructor(private val repository: HomePageRepository,
                                                 @Assisted private val savedStateHandle: SavedStateHandle)
: ViewModel(){}

и нужно инициализировать модель просмотра следующим образом: private lateinit var viewModel: MainViewModel viewModel = ViewModelProviders.of (this) .get (MainViewModel :: class.java)

Используйте это напрямую:

частный val mainViewModel: MainViewModel от activityViewModels ()

Объяснение: поддерживаемый дескриптор сохраненного состояния: гарантирует, что если действие / фрагмент аннотируется точкой входа @Android в сочетании с введением модели представления, он автоматически внедрит все необходимые зависимости конструктора, доступные из соответствующего действия / приложения компонента. так что нам не придется передавать эти параметры при инициализации модели просмотра во фрагменте / активности

person Medha    schedule 04.01.2021