I'm using Dagger-Hilt for dependency injection in my Android project, now I have this situation where I have a base abstract Fragment
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.
@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.
abstract class BaseViewModel: ViewModel() {
@Inject
lateinit var api: FakeApi
//...
}
@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
super
class instead of passing the dependency from the child
?Inject ViewModel objects with HiltProvide a ViewModel by annotating it with @HiltViewModel and using the @Inject annotation in the ViewModel object's constructor. Note: To use Dagger's assisted injection with ViewModels, see the following Github issue.
To perform field injection, Hilt needs to know how to provide instances of the necessary dependencies from the corresponding component. A binding contains the information necessary to provide instances of a type as a dependency. The parameters of an annotated constructor of a class are the dependencies of that class.
The viewModels() extension function is provided by androidx KTX . Even if the ViewModel has savedStateHandle or is taking the intent bundle from the Activity, it doesn't need to be explicitly injected in. The viewModels() extension function does all that automatically behind the scene.
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.
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
}
}
Or you can use the by lazy
to declare your properties.
I tested and I see that field injection in base class
still work with Hilt 2.35. I can not get the error like you so maybe you can try to change the Hilt version or check how you provide 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With