i am try to inject a module to MyViewModel
here is my Module
#Module
#InstallIn(ViewModelComponent::class)
object EngineModule {
#Provides
fun getEngine(): String = "F35 Engine"
}
and this my viewModel
#HiltViewModel
class MyViewModel #Inject constructor(): ViewModel() {
#Inject lateinit var getEngine: String
fun getEngineNameFromViewModel(): String = getEngineName()
}
and it throws
kotlin.UninitializedPropertyAccessException: lateinit property getEngine
has not been initialized
however if i change ViewModelComponent::class to ActivityComponent::class and inject like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject
lateinit var getEngine: String
it works perfectly
any idea how to inject viewModels?
Also you can just remove #Inject constructor since you are already providing the dependency using dagger module:
#HiltViewModel
class MyViewModel (private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
So, Basically you can either provide the dependency using dagger module or constructor injection.
Since required dependency is going to be injected in the ViewModel's constructor, you just need to modify your code in the following way to make it work:
#HiltViewModel
class MyViewModel #Inject constructor(private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
Related
Based on the Hilt tutorial, ViewModels needs to be inject the following way:
#HiltViewModel
class ExampleViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() {
...
}
However, in my case, I want to use an interface:
interface ExampleViewModel()
#HiltViewModel
class ExampleViewModelImp #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
...
}
Then I want to inject it via the interface
#AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
How to make this work?
viewModels requires child of ViewModel class
val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()
Had a similar problem where I wanted to Inject the ViewModel via interface, primarily because to switch it with a fake implementation while testing. We are migrating from Dagger Android to Hilt, and we had UI tests that used fake view models. Adding my findings here so that it could help someone whose facing a similar problem.
Both by viewModels() and ViewModelProviders.of(...) expects a type that extends ViewModel(). So interface won't be possible, but we can still use an abstract class that extends ViewModel()
I don't think there is a way to use #HiltViewModel for this purpose, since there was no way to switch the implementation.
So instead, try to inject the ViewModelFactory in the Fragment. You can switch the factory during testing and thereby switch the ViewModel.
#AndroidEntryPoint
class ListFragment : Fragment() {
#ListFragmentQualifier
#Inject
lateinit var factory: AbstractSavedStateViewModelFactory
private val viewModel: ListViewModel by viewModels(
factoryProducer = { factory }
)
}
abstract class ListViewModel : ViewModel() {
abstract fun load()
abstract val title: LiveData<String>
}
class ListViewModelImpl(
private val savedStateHandle: SavedStateHandle
) : ListViewModel() {
override val title: MutableLiveData<String> = MutableLiveData()
override fun load() {
title.value = "Actual Implementation"
}
}
class ListViewModelFactory(
owner: SavedStateRegistryOwner,
args: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, args) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return ListViewModelImpl(handle) as T
}
}
#Module
#InstallIn(FragmentComponent::class)
object ListDI {
#ListFragmentQualifier
#Provides
fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
return ListViewModelFactory(fragment, fragment.arguments)
}
}
#Qualifier
annotation class ListFragmentQualifier
Here, ListViewModel is the abstract class and ListViewModelImpl is the actual implementation. You can switch the ListDI module while testing using TestInstallIn. For more information on this, and a working project refer to this article
Found a solution using HiltViewModel as a proxy to the actual class I wish to inject. It is simple and works like a charm ;)
Module
#Module
#InstallIn(ViewModelComponent::class)
object MyClassModule{
#Provides
fun provideMyClas(): MyClass = MyClassImp()
}
class MyClassImp : MyClass {
// your magic goes here
}
Fragment
#HiltViewModel
class Proxy #Inject constructor(val ref: MyClass) : ViewModel()
#AndroidEntryPoint
class MyFragment : Fragment() {
private val myClass by lazy {
val viewModel by viewModels<Proxy>()
viewModel.ref
}
}
Now you got myClass of the type MyClass interface bounded to viewModels<Proxy>() lifeCycle
It's so simple to inject an interface, you pass an interface but the injection injects an Impl.
#InstallIn(ViewModelComponent::class)
#Module
class DIModule {
#Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()
}
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?
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.
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
After many searches on the Internet, I think the best solution is to not use initializer blocks init { ... } on the ViewModel, and instead create a function fun initialize() { ... } that will be called on the Fragment.
BaseViewModel.kt
#HiltViewModel
open class BaseViewModel #Inject constructor() : ViewModel() {
#Inject
protected lateinit var localUserRepository: LocalUserRepository
}
OnboardingViewModel.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()
}
}
}
OnBoardingFragment.kt
#AndroidEntryPoint
class OnBoardingFragment() {
override val viewModel: OnboardingViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.initialize()
}
}
Sources:
https://github.com/google/dagger/issues/2507
the answers on this question
I have a ViewModelFactory implemented as follows:
class ViewModelFactory<VM> #Inject constructor(private val viewModel: Lazy<VM>)
: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
#Suppress("UNCHECKED_CAST")
return viewModel.get() as T
}
}
This works fine with my current ViewModel:
class MainActivityViewModel #Inject constructor(private val dependency: Dependency) : ViewModel()
//... in the activity:
#Inject
lateinit var factory: ViewModelFactory<MainActivityViewModel>
private val viewModel: MainActivityViewModel by viewModels { factory }
However I have a different build flavour that I want to implement where the behaviour is different, so I have created an AbstractViewModel:
abstract class AbstractViewModel : ViewModel()
//...and so now
class MainActivityViewModel #Inject constructor(private val dependency: Dependency) : AbstractViewModel()
//... and in the activity
#Inject
lateinit var factory: ViewModelFactory<AbstractViewModel>
private val viewModel: AbstractViewModel by viewModels { factory }
I want to be able to provide the specific instance to the ViewModelFactory, but I am not sure how to achieve this.
Solved it. Answering for future reference and anyone who may be interested in doing something similar.
Step 1: Add a new module component
#Component(
modules = [
//...
ViewModelModule::class
]
)
interface ApplicationComponent { //...
This allows developers to create a FlavourApplicationComponent that can have an alternate to ViewModelModule, which I called MockViewModelModule
Step 2: Define the modules
//in the main flavour
#Module
class ViewModelModule {
#Provides
fun provideMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): AbstractViewModel
= mainActivityViewModel
}
//in the mock flavour
#Module
class MockViewModelModule {
#Provides
fun provideMainActivityViewModel(mainActivityViewModel: MainActivityViewModel): AbstractViewModel
= MockMainActivityViewModel()
}
And this can actually be configured at run time, and therefore you can allow users to test all the different states of your app without them needing to GET into those states.
I have an Android project with Hilt dependency injection. I have defined MyApplication and MyModule as follows.
#HiltAndroidApp
class MyApplication : Application()
#Module
#InstallIn(ApplicationComponent::class)
abstract class MyModule {
#Binds
#Singleton
abstract fun bindMyRepository(
myRepositoryImpl: MyRepositoryImpl
): MyRepository
}
MyRepositoryImpl implements the MyRepository interface:
interface MyRepository {
fun doSomething(): String
}
class MyRepositoryImpl
#Inject
constructor(
) : MyRepository {
override fun doSomething() = ""
}
I can now inject this implementation of MyRepository into a ViewModel:
class MyActivityViewModel
#ViewModelInject
constructor(
private val myRepository: MyRepository,
) : ViewModel() { }
This works as expected. However, if I try to inject the repository into a service, I get an error java.lang.Class<MyService> has no zero argument constructor:
class MyService
#Inject
constructor(
private val myRepository: MyRepository,
): Service() { }
The same error occurs with an activity, too:
class MyActivity
#Inject
constructor(
private val myRepository: MyRepository,
) : AppCompatActivity(R.layout.my_layout) { }
What am I doing wrong with the injection?
From the documentation on how we Inject dependencies into Android classes, we can learn the following:
Hilt can provide dependencies to other Android classes that have the #AndroidEntryPoint annotation.
Hilt currently supports the following Android classes:
Application (by using #HiltAndroidApp)
ViewModel (by using #HiltViewModel)
Activity
Fragment
View
Service
BroadcastReceiver
So when you subclass any of these Android classes, you don't ask Hilt to inject dependencies through the constructors. Instead, you annotate it with #AndroidEntryPoint, and ask Hilt to inject its dependencies by annotating the property with #Inject:
#AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
#Inject
lateinit var mAdapter: SomeAdapter
...
}
So, in your case you should inject MyRepository in MyActivity and MyService like this:
#AndroidEntryPoint
class MyService: Service() {
#Inject
lateinit var myRepository: MyRepository
...
}
#AndroidEntryPoint
class MyActivity: AppCompatActivity(R.layout.my_layout) {
#Inject
lateinit var myRepository: MyRepository
...
}
And remember:
Fields injected by Hilt cannot be private
That's it for Android classes that is supported by Hilt.
If you wonder what about classes not supported by Hilt (ex: ContentProvider)?! I recommend learning how from this tutorial #EntryPoint annotation on codelab (also don't forget to check the documentation for how to Inject dependencies in classes not supported by Hilt).
Also If you overriding onCreate in your Service - don't forget to add super.onCreate() otherwise you will get runtime exception that the myRepository value cannot be initialised (I was struggling with this error for some time during refactoring my service so maybe it will be helpful for somebody)
#AndroidEntryPoint
class MyService : Service() {
#Inject
lateinit var myRepository: MyRepository
override fun onCreate() {
super.onCreate()
}
}
Your use of #Inject on the MyService class is as if MyService is to be injected at some other location.
If I understand correctly, you want something more akin to:
#AndroidEntryPoint
class MyService : Service() {
#Inject
lateinit var myRepository: MyRepository
}
I am facing this issue in multi module android project with HILT.
kotlin.UninitializedPropertyAccessException: lateinit property repository has not been initialized in MyViewModel
My modules are
App Module
Viewmodel module
UseCase Module
DataSource Module
'App Module'
#AndroidEntryPoint
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.test()
}}
'ViewModel Module'
class MainViewModel #ViewModelInject constructor(private val repository: MyUsecase): ViewModel() {
fun test(){
repository.test()
}}
'UseCase Module'
class MyUsecase #Inject constructor() {
#Inject
lateinit var feature: Feature
fun doThing() {
feature.doThing()
}
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
}
'DataSource Module'
interface Feature {
fun doThing()
}
class RealFeature : Feature {
override fun doThing() {
Log.v("Feature", "Doing the thing!")
}
}
Dependencies are
MyFragment ---> MyViewModel ---> MyUseCase ---> DataSource
what i did wrong with this code pls correct it.
above your activity class you must add annotation #AndroidEntryPoint
as below:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
In addition to moving all your stuff to constructor injection, your RealFeature isn't being injected, because you instantiate it manually rather than letting Dagger construct it for you. Note how your FeatureModule directly calls the constructor for RealFeature and returns it for the #Provides method. Dagger will use this object as is, since it thinks you've done all the setup for it. Field injection only works if you let Dagger construct it.
Your FeatureModule should look like this:
#Module
#InstallIn(ApplicationComponent::class)
object FeatureModule {
#Provides
fun feature(realFeature: RealFeature): Feature = realFeature
}
Or with the #Binds annotation:
#Module
#InstallIn(ApplicationComponent::class)
interface FeatureModule {
#Binds
fun feature(realFeature: RealFeature): Feature
}
This also highlights why you should move to constructor injection; with constructor injection, this mistake wouldn't have been possible.
The problem in the code is that #ViewModelInject doesn't work as #Inject in other classes. You cannot perform field injection in a ViewModel.
You should do:
class MainViewModel #ViewModelInject constructor(
private val myUseCase: MyUsecase
): ViewModel() {
fun test(){
myUseCase.test()
}
}
Consider following the same pattern for the MyUsecase class. Dependencies should be passed in in the constructor instead of being #Injected in the class body. This kind of defeats the purpose of dependency injection.
First, i think you are missing #Inject on your RealFeature class, so the Hilt knows how the inject the dependency. Second, if you want to inject into a class that is not a part of Hilt supported Entry points, you need to define your own entry point for that class.
In addition to the module that you wrote with #Provides method, you need to tell Hilt how the dependency can be accessed.
In your case you should try something like this:
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface FeatureInterface {
fun getFeatureClass(): Feature
}
Then, when you want to use it, write something like this:
val featureInterface =
EntryPoints.get(appContext, FeatureInterface::class.java)
val realFeature = featureInterface.getFeatureClass()
You can find more info here:
https://dagger.dev/hilt/entry-points
https://developer.android.com/training/dependency-injection/hilt-android#not-supported
class MainViewModel #ViewModelInject constructor(private val repository: HomePageRepository,
#Assisted private val savedStateHandle: SavedStateHandle)
: ViewModel(){}
and intead of initializing the viewmodel like this :
private lateinit var viewModel: MainViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
Use this directly :
private val mainViewModel:MainViewModel by activityViewModels()
EXplanation :
assisted saved state handle : will make sure that if activity / fragment is annotated with #Android entry point combined with view model inject , it will automatically inject all required constructor dependencies available from corresonding component activity / application so that we won't have to pass these parameters while initializing viewmodel in fragment / activity
Make sure you added class path and plugin
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35'
in Project.gradle
apply plugin: 'dagger.hilt.android.plugin'
in app.gradle