Dagger2 + Kotlin: lateinit property has not been initialized
Asked Answered
U

7

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()
    }
}
Uzziel answered 27/5, 2018 at 17:21 Comment(0)
S
38

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)

    ...
}
Suber answered 27/5, 2018 at 17:40 Comment(6)
Thank you! I'm learning Dagger and somethings are not so clear yet. Your answer and explanation helped a lot!Uzziel
Check out my tutorial series, starting here: dev.to/autonomousapps/…Vigue
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.Aixenprovence
@AndroidDev Dagger will inject those fields IF REQUESTED, aka when you call component.inject(this). Kotlin has nothing to do with this.Strobilaceous
@Strobilaceous 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 @Injected field, it still allows project to compileAsepsis
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.Strobilaceous
O
14

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

<application
    android:name=".YourAppName"
    ..../>
Overplus answered 28/9, 2020 at 13:19 Comment(2)
after all day spending time on dagger2 to find issue, I see this comment and added application class to manifest. it was silly mistake but ever thoughtCollotype
WTF! How did U guess??? You saved my brain! THANK YOU!Quintan
H
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>)
Handal answered 8/7, 2018 at 23:19 Comment(0)
F
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()
        })
    }
}
Foggia answered 11/2, 2020 at 12:31 Comment(1)
This only works if you set up the @ContributesAndroidInjector and the HasActivityinjectors.Strobilaceous
F
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

Fret answered 3/4, 2020 at 15:40 Comment(0)
S
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)
}
Salford answered 6/6, 2023 at 10:58 Comment(0)
F
-1

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

Filet answered 18/1, 2021 at 1:11 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.