Issue
I'm using Dagger-Hilt for dependency injection in my Android project, now I have this situation where I have a base abstract Fragment
BaseViewModel.kt
abstract class BaseViewModel constructor(
val api: FakeApi,
) : ViewModel() {
//...
}
Here, I have a dependency which is FakeApi
. What I'm trying to do is to inject the FakeApi
into the BaseViewModel
to be available in the BaseViewModel
and all its children.
- The first approach I tried is using the constructor injection and inject it to the child and pass it to the super using the constructor.
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(
api: FakeApi
) : BaseViewModel(api){
}
This approach works fine, but I don't need to pass the dependency from the child
to the super
class, I need the FakeApi
to be automatically injected in the BaseViewModel
without having to pass it as I have three levels of abstraction (There is another class inheriting from the TaskViewModel) So I have to pass it two times.
- The second approach was to use the field injection as follows
BaseViewModel.kt
abstract class BaseViewModel: ViewModel() {
@Inject
lateinit var api: FakeApi
//...
}
TaskViewModel.kt
@HiltViewModel
class TaskViewModel @Inject constructor(): BaseViewModel() {
}
This approach didn't work for me and the FakeApi
wasn't injected and I've got an Exception
kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized
My questions are
- Why field injection doesn't work for me?
- Is there any way to use constructor injection for the
super
class instead of passing the dependency from thechild
?
Solution
Thanks to this Github Issue I figured out that the problem is that you can't use the field injected properties during the ViewModel constructor initialization, but you still use it after the constructor -including all the properties direct initialization- has been initialized.
Dagger firstly completes the constructor injection process then the field injection process takes place. that's why you can't use the field injection before the constructor injection is completed.
❌ Wrong use
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
}
}
✔️ Right use
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
}
}
Or you can use the by lazy
to declare your properties.
Answered By - Hamza Sharuf