在超级 Fragment 或 ViewModel 中注入 Hilt 字段
Hilt field injection in the super Fragment or ViewModel
我在我的 Android 项目中使用 Dagger-Hilt 进行依赖注入,现在我有一个基本抽象片段
BaseViewModel.kt
abstract class BaseViewModel constructor(
val api: FakeApi,
) : ViewModel() {
//...
}
在这里,我有一个依赖项 FakeApi
。我想要做的是将 FakeApi
注入 BaseViewModel
以供 BaseViewModel
及其所有子项使用。
- 我尝试的第一种方法是使用构造函数注入并将其注入子对象并使用构造函数将其传递给超级对象。
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(
api: FakeApi
) : BaseViewModel(api){
}
这种方法工作正常,但我不需要将依赖项从 child
传递到 super
class,我需要 FakeApi
自动注入 BaseViewModel
而不必传递它,因为我有三个抽象级别(还有另一个 class 继承自 TaskViewModel)所以我必须传递它两次。
- 第二种方法是使用字段注入,如下所示
BaseViewModel.kt
abstract class BaseViewModel: ViewModel() {
@Inject
lateinit var api: FakeApi
//...
}
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(): BaseViewModel() {
}
这种方法对我不起作用,FakeApi
没有注入,我有一个 Exception
kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized
我的问题是
- 为什么字段注入对我不起作用?
- 有没有什么方法可以为
super
class 使用构造函数注入而不是传递 child
的依赖项?
我进行了测试,发现 base class
中的字段注入仍然适用于 Hilt 2.35。我无法得到像你这样的错误,所以也许你可以尝试更改 Hilt 版本或检查你如何提供 FakeApi
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
}
FakeApi
// Inject constructor also working
class FakeApi {
fun doSomeThing() {
Log.i("TAG", "do something")
}
}
MainViewModel
@HiltViewModel
class MainViewModel @Inject constructor() : BaseViewModel() {
// from activity, when I call this function, the logcat print normally
fun doSomeThing() {
fakeApi.doSomeThing()
}
}
AppModule
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
fun provideAPI(
): FakeApi {
return FakeApi()
}
}
感谢这个 Github Issue 我发现问题是你不能在 ViewModel 构造函数初始化期间使用字段注入属性,但你仍然在构造函数之后使用它 - 包括直接的所有属性初始化- 已经初始化。
Dagger 首先完成构造函数注入过程,然后进行字段注入过程。这就是为什么在构造函数注入完成之前不能使用字段注入的原因。
❌ 错误使用
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
val temp = fakeApi.doSomething() // Don't use it in direct property declaration
init {
fakeApi.doSomething() // Don't use it in the init block
}
}
✔️正确使用
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
val temp: Any
get() = fakeApi.doSomething() // Use property getter
fun doSomething(){
fakeApi.doSomething() // Use it after constructor initialization
}
}
或者您可以使用 by lazy
声明您的属性。
我在我的 Android 项目中使用 Dagger-Hilt 进行依赖注入,现在我有一个基本抽象片段
BaseViewModel.kt
abstract class BaseViewModel constructor(
val api: FakeApi,
) : ViewModel() {
//...
}
在这里,我有一个依赖项 FakeApi
。我想要做的是将 FakeApi
注入 BaseViewModel
以供 BaseViewModel
及其所有子项使用。
- 我尝试的第一种方法是使用构造函数注入并将其注入子对象并使用构造函数将其传递给超级对象。
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(
api: FakeApi
) : BaseViewModel(api){
}
这种方法工作正常,但我不需要将依赖项从 child
传递到 super
class,我需要 FakeApi
自动注入 BaseViewModel
而不必传递它,因为我有三个抽象级别(还有另一个 class 继承自 TaskViewModel)所以我必须传递它两次。
- 第二种方法是使用字段注入,如下所示
BaseViewModel.kt
abstract class BaseViewModel: ViewModel() {
@Inject
lateinit var api: FakeApi
//...
}
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(): BaseViewModel() {
}
这种方法对我不起作用,FakeApi
没有注入,我有一个 Exception
kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized
我的问题是
- 为什么字段注入对我不起作用?
- 有没有什么方法可以为
super
class 使用构造函数注入而不是传递child
的依赖项?
我进行了测试,发现 base class
中的字段注入仍然适用于 Hilt 2.35。我无法得到像你这样的错误,所以也许你可以尝试更改 Hilt 版本或检查你如何提供 FakeApi
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
}
FakeApi
// Inject constructor also working
class FakeApi {
fun doSomeThing() {
Log.i("TAG", "do something")
}
}
MainViewModel
@HiltViewModel
class MainViewModel @Inject constructor() : BaseViewModel() {
// from activity, when I call this function, the logcat print normally
fun doSomeThing() {
fakeApi.doSomeThing()
}
}
AppModule
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
@Provides
fun provideAPI(
): FakeApi {
return FakeApi()
}
}
感谢这个 Github Issue 我发现问题是你不能在 ViewModel 构造函数初始化期间使用字段注入属性,但你仍然在构造函数之后使用它 - 包括直接的所有属性初始化- 已经初始化。
Dagger 首先完成构造函数注入过程,然后进行字段注入过程。这就是为什么在构造函数注入完成之前不能使用字段注入的原因。
❌ 错误使用
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
val temp = fakeApi.doSomething() // Don't use it in direct property declaration
init {
fakeApi.doSomething() // Don't use it in the init block
}
}
✔️正确使用
abstract class BaseViewModel : ViewModel() {
@Inject
protected lateinit var fakeApi: FakeApi
val temp: Any
get() = fakeApi.doSomething() // Use property getter
fun doSomething(){
fakeApi.doSomething() // Use it after constructor initialization
}
}
或者您可以使用 by lazy
声明您的属性。