38

I'm trying to inject the ViewModelFactory into my Activity, but it keeps throwing this same error: lateinit property viewModelFactory has not been initialized. I can't find what I may be doing wrong. See the code above from my classes

AppComponent.kt

@Component(modules = [(AppModule::class), (NetworkModule::class), (MainModule::class)])
interface AppComponent {

    fun inject(application: TweetSentimentsApplication)

    fun inject(mainActivity: MainActivity)

    fun context(): Context

    fun retrofit(): Retrofit
}

MainModule.kt

@Module
class MainModule {

    @Provides
    fun mainViewModelFactorty(repository: TweetRepository): MainViewModelFactory = MainViewModelFactory(repository)

    @Provides
    fun local(database: AppDatabase): TweetLocal = TweetLocal(database)

    @Provides
    fun remote(tweetService: TweetService): TweetRemote = TweetRemote(tweetService)

    @Provides
    fun tweetService(retrofit: Retrofit): TweetService = retrofit.create(TweetService::class.java)

    @Provides
    fun repository(local: TweetLocal, remote: TweetRemote): TweetRepository = TweetRepository(local, remote)

}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    @Inject lateinit var viewModelFactory: MainViewModelFactory

    private val viewModel: MainViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)

        viewModel?.init("guuilp")
        viewModel?.getTweetList()?.observe(this, Observer {
            Toast.makeText(this, it?.size.toString(), Toast.LENGTH_LONG).show()
        })
    }
}

TweetSentimentsApplication.kt

open class TweetSentimentsApplication: Application(){

    companion object {
        lateinit var appComponent: AppComponent
    }

    override fun onCreate() {
        super.onCreate()

        initDI()
    }

    private fun initDI() {
        appComponent = DaggerAppComponent.builder()
                .appModule(AppModule(this))
                .build()
    }
}
Guilherme Lima Pereira
  • 1,402
  • 5
  • 17
  • 35

7 Answers7

37

You have to call the inject(mainActivity: MainActivity) method you've defined in AppComponent when you're initializing your MainActivity, that's how Dagger actually injects the dependencies you need.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_activity)

    // This is where the dependencies are injected
    TweetSentimentsApplication.appComponent.inject(this)

    ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)

    ...
}
zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • Thank you! I'm learning Dagger and somethings are not so clear yet. Your answer and explanation helped a lot! – Guilherme Lima Pereira May 27 '18 at 17:42
  • Check out my tutorial series, starting here: https://dev.to/autonomousapps/the-daggerandroid-missing-documentation-33kj – AutonomousApps May 27 '18 at 17:45
  • 3
    Actually according to the docs, that is not true. Dagger is suppose to inject dependencies without requiring you to perform an "inject" - "If your class has Inject-annotated fields but no Inject-annotated constructor, Dagger will inject those fields if requested, but will not create new instances. Add a no-argument constructor with the @Inject annotation to indicate that Dagger may create instances as well." - It appears that Kotlin violates this statement. – Johann May 21 '19 at 15:01
  • @AndroidDev Dagger will inject those fields ***IF REQUESTED***, aka when you call `component.inject(this)`. Kotlin has nothing to do with this. – EpicPandaForce Feb 11 '20 at 12:37
  • @EpicPandaForce What you said applies to only Android Components in this context. That, probably, means Dagger is not as runtime-safe as it's advertised. Although it can't assign an instance to the `@Inject`ed field, it still allows project to compile – Farid Jun 24 '20 at 15:02
  • That's field injection for ya. If it weren't Android and you could just say "go, main function!" then you'd be able to use constructor injection everywhere and it would work great. – EpicPandaForce Jun 24 '20 at 15:26
14

Also, make sure that your application name is added in the AndroidManifest.xml file.

<application
    android:name=".YourAppName"
    ..../>
Ritesh R
  • 198
  • 1
  • 10
5

You can also do this:

  @Inject
  lateinit var viewModelFactory: ViewModelProvider.Factory
  val mainViewModel: MainViewModel by lazy {
      ViewModelProviders.of(this, viewModelFactory)[MainViewModel::class.java]
  }

and use abstract modules with @ContributesAndroidInjector for the activity, and abstract module for view model. Using abstract is more efficient:

   @Module
   abstract class AndroidBindingModule {

   @ContributesAndroidInjector
    internal abstract fun contributesAnActivity(): AnActivity
    }



 @Module
    abstract class ViewModelModule {
      //the default factory only works with default constructor
      @Binds
      @IntoMap
      @ViewModelKey(AViewModel::class)
      abstract fun bindArtViewModel(aViewModel: AViewModel): ViewModel

      @Binds
      abstract fun bindViewModelFactory(factory: AViewModelFactory): ViewModelProvider.Factory
    }





 @Documented
    @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Droid Teahouse
  • 893
  • 8
  • 15
1

You could also extend DaggerAppCompatActivity in place of AppCompatActivity. E.g.

class MainActivity : DaggerAppCompatActivity() {

    @Inject lateinit var viewModelFactory: MainViewModelFactory

    private val viewModel: MainViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        ViewModelProviders.of(this, viewModelFactory).get(MainViewModel::class.java)

        viewModel?.init("guuilp")
        viewModel?.getTweetList()?.observe(this, Observer {
            Toast.makeText(this, it?.size.toString(), Toast.LENGTH_LONG).show()
        })
    }
}
0

My mistake was creating a new object and taking a component from it such as App().component.

So in this situation you need to put component field in companion object and replace code with App.component

Neuron
  • 1,020
  • 2
  • 13
  • 28
0

I had this problem because of wrong signature of inject() method, where I mistakenly declared parameter type as interface which my class was implementing, instead of the class itself. That caused empty inject() implementation by Dagger, so the dependencies were not injected -> properties not initialized.

The class where dependencies are injected:

interface MyInterface {
    ...
}

class MyClass : MyInterface {

    @Inject
    lateinit var someProperty: SomeType

    init {
        ...
        myComponent.inject(this)
        ...
        someProperty.doSomething() // error: lateinit property has not been initialized
    }
}

Wrong inject() declaration:

interface MyComponent {
    fun inject(x: MyInterface)
}

Changing parameter type in the inject() declaration solved the problem.

interface MyComponent {
    fun inject(x: MyClass)
}
dakuljan
  • 41
  • 5
-1

Maybe you missed implementing "Injectable" Interface in fragment/activity. Which marks fragment/activity as injectable.

Sikander Bakht
  • 239
  • 2
  • 8