android 超级Fragment或ViewModel中的柄字段注入

hec6srdp  于 2022-12-28  发布在  Android
关注(0)|答案(3)|浏览(148)

我在我的Android项目中使用Dagger-Hilt进行依赖注入,现在我有一个基本抽象片段

基本视图模型.kt

abstract class BaseViewModel constructor(
    val api: FakeApi,
) : ViewModel() {
    
    //...
    
}

这里,我有一个依赖项FakeApi,我尝试做的是将FakeApi注入到BaseViewModel中,以便在BaseViewModel及其所有子对象中可用。

  • 我尝试的第一种方法是使用构造函数注入,将其注入到子对象,然后使用构造函数将其传递给超级对象。

任务视图模型.kt

@HiltViewModel
class TaskViewModel @Inject constructor(
    api: FakeApi
) : BaseViewModel(api){

}

这种方法工作正常,但我不需要将依赖项从child传递到super类,我需要将FakeApi自动注入BaseViewModel,而不必传递它,因为我有三个抽象级别(有另一个类继承自TaskViewModel),所以我必须传递它两次。

  • 第二种方法是按如下方式使用场注入

基本视图模型.kt

abstract class BaseViewModel: ViewModel() {
    @Inject
    lateinit var api: FakeApi
    //...
}

任务视图模型.kt

@HiltViewModel
class TaskViewModel @Inject constructor(): BaseViewModel() {
    
}

这种方法对我不起作用,没有注入FakeApi,我得到了Exception

kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized

我的问题是

  • 为什么磁场注射对我不起作用?
  • 有没有办法对super类使用构造函数注入,而不是从child传递依赖项?
8tntrjer

8tntrjer1#

多亏了这个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来声明属性。

8wigbo56

8wigbo562#

我进行了测试,发现base class中的场注入仍然适用于Hilt 2.35。我无法像您一样获得错误,因此您可以尝试更改Hilt版本或检查您提供FakeApi的方式

abstract class BaseViewModel : ViewModel() {

    @Inject
    protected lateinit var 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

ego6inou

ego6inou3#

在互联网上搜索了很多次之后,我认为最好的解决方案是不在ViewModel上使用初始化块init { ... },而是创建一个将在Fragment上调用的函数fun initialize() { ... }

基本视图模型.kt

@HiltViewModel
open class BaseViewModel @Inject constructor() : ViewModel() {
    @Inject
    protected lateinit var localUserRepository: LocalUserRepository
}

板载视图模型.kt

@HiltViewModel
class OnboardingViewModel @Inject constructor() : BaseViewModel() {
    // Warning: don't use "init {}", the app will crash because of BaseViewModel
    // injected properties not initialized
    fun initialize() {
        if (localUserRepository.isLoggedIn()) {
            navigateToHomeScreen()
        }
    }
}

载入片段.kt

@AndroidEntryPoint
class OnBoardingFragment() {

    override val viewModel: OnboardingViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.initialize()
    }
}

资料来源:

相关问题