Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hilt field injection in the super Fragment or ViewModel

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 the child?
like image 834
Hamza Sharuf Avatar asked Jun 24 '21 14:06

Hamza Sharuf


People also ask

How do you inject a ViewModel in hilt?

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.

How do you field injection in hilt?

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.

Should ViewModel be injected?

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.


2 Answers

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.

like image 65
Hamza Sharuf Avatar answered Oct 26 '22 14:10

Hamza Sharuf


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

like image 20
Linh Avatar answered Oct 26 '22 13:10

Linh