【问题标题】:Hilt field injection in the super Fragment or ViewModel超级 Fragment 或 ViewModel 中的 Hilt 字段注入
【发布时间】:2021-06-24 14:27:22
【问题描述】:

我在我的 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 类,我需要将FakeApi 自动注入BaseViewModel 而不必传递它因为我有三个抽象级别(还有一个继承自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 类使用构造函数注入,而不是从child 传递依赖关系?

【问题讨论】:

  • 您的错误中提到了communicationHandler,但您的问题中没有提及?
  • TaskViewModel 在您的第二种方法中看起来像什么?
  • @HenryTwist 这是另一个没有提到的依赖项。我编辑了问题。
  • 你能改变你的文字和标题吗?你说你有一个基本片段,但它说“baseviewmodel”。这很烦人

标签: android dependency-injection dagger-hilt


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    主视图模型

    @HiltViewModel
    class MainViewModel @Inject constructor() : BaseViewModel() {
    
        // from activity, when I call this function, the logcat print normally 
        fun doSomeThing() {
            fakeApi.doSomeThing()
        }
    }
    

    应用模块

    @Module
    @InstallIn(SingletonComponent::class)
    class AppModule {
    
        @Provides
        fun provideAPI(
        ): FakeApi {
            return FakeApi()
        }
    }
    

    https://github.com/PhanVanLinh/AndroidHiltInjectInBaseClass

    【讨论】:

    • 您使用的是哪个版本的 Hilt?我目前面临同样的问题,您的解决方案使 jvm 屈服:lateinit property has not been initialized
    • @Fattum,我的回答最后有一个github repo,也许你可以查一下,希望对你有帮助
    • 我明白了。谢谢你。我想我知道我的问题可能出在哪里。我正在尝试在撰写环境中使用您的方法。我通过 hiltViewModel() 调用获得了我的 viewModel。也许,hilt-compose 依赖项中有问题。
    • 这很好,但是,如果我尝试在 init 块中使用注入的属性(api) 或直接初始化一个属性,它将抛出相同的UninitializedPropertyAccessException。你知道这是什么原因吗?为什么在类或 ViewModel 初始化期间不能使用字段注入属性?
    • 我刚刚又测试了一遍,看起来字段注入对于普通的类或活动来说效果很好,只是在ViewModel初始化期间使用它会导致问题。还是不知道为什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-24
    相关资源
    最近更新 更多