HILT : lateinit property repository has not been initialized in ViewModel

Viewed 10746

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

  1. App Module
  2. Viewmodel module
  3. UseCase Module
  4. 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.

6 Answers

above your activity class you must add annotation @AndroidEntryPoint as below:

@AndroidEntryPoint class MainActivity : AppCompatActivity() {

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.

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.

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

Related