在超级 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

我的问题是

我进行了测试,发现 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()
    }
}

https://github.com/PhanVanLinh/AndroidHiltInjectInBaseClass

感谢这个 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 声明您的属性。