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()
}
}
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)
...
}
Also, make sure that your application name is added in the AndroidManifest.xml file.
<application
android:name=".YourAppName"
..../>
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>)
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()
})
}
}
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
Maybe you missed implementing "Injectable" Interface in fragment/activity. Which marks fragment/activity as injectable.
Related
I'm using dagger 2.25.2 and androidx.lifecycle for dependency injection in my android project. But I've got error ViewModelFactory has not been initialized
Here is my code's
class PropertyActivity : AppCompatActivity() {
#Inject
lateinit var propertyViewModelFactory: PropertyViewModelFactory
lateinit var propertyViewModel: PropertyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.property_activity)
propertyViewModel = ViewModelProviders.of(this, propertyViewModelFactory).get(
PropertyViewModel::class.java)
propertyViewModel.loadProperties()
}
ViewModelFactory:
class PropertyViewModelFactory #Inject constructor(
private val propertyViewModel: PropertyViewModel) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PropertyViewModel::class.java!!)) {
return propertyViewModel as T
}
throw IllegalArgumentException("Unknown class name")
}
}
AppModule:
#Module
class AppModule(val app: Application){
#Provides
#Singleton
fun provideApplication(): Application = app
#Provides
#Singleton
fun providePropertyViewModelFactory(factory: PropertyViewModelFactory): ViewModelProvider.Factory = factory
}
BuildersModule:
#Module
abstract class BuildersModule {
#ContributesAndroidInjector
abstract fun contributePropertyActivity(): PropertyActivity
}
AppComponent:
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class,BuildersModule::class, AppModule::class))interface PlotComponent {
fun inject(app: Application)
}
Application Class :
class PlotApplication : Application(), HasAndroidInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerPlotComponent.builder()
.appModule(AppModule(this))
.build().inject(this)
}
override fun androidInjector(): AndroidInjector<Any> = activityInjector
}
I doesn't see where you inject dependency in your activity. The problem may be in this.
Add
AndroidInjection.inject(this)
in your onCreate
I'm new using Dagger2 (I always used Koin) and I'm trying to implement a simple sample but I don't really know what I'm missing. This is what I got so far.
app.gradle:
ext.daggerVersion = '2.23.2'
implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
AppModule.kt:
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Application = app
#Provides
#Singleton
fun provideTestOperator(testOperator: TestOperator) = testOperator
#Provides
#Singleton
fun provideTestClass(testClass: TestClass) = testClass
}
AppComponent.kt:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: App): Builder
fun build(): AppComponent
}
}
TestClass.kt & TestOperator.kt in the same file:
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this#App).build()
}
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
Error: testClass == null
AppModule.kt: Provide the application context. No need to write #singleton #provides for your Test* classes (will see why)
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Context = app.applicationContext
}
AppComponent.kt: #Component.Builder is deprecated IIRC. Use #Component.Factory. And replace AndroidInjectionModule::class with AndroidSupportInjectionModule::class since we are using dagger-android-support and android's *Compat* stuff. Refer a new module here called ActivityModule::class.
#Singleton
#Component(modules = [
ActivityModule::class
AndroidSupportInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Factory
abstract class Factory : AndroidInjector.Factory<App>
}
TestClass.kt & TestOperator.kt: Since you were providing singletons by writing #singleton and #provides method, I assume you want them to be singletons. Just annotate the class definition with #Singleton and dagger will take care of it. No need to write #Provides methods.
#Singleton
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
#Singleton
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt: Using factory instead of builder since #Component.Builder is deprecated.
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this)
}
}
ActivityModule.kt: Provide a module to dagger to create your activities.
#Module
interface ActivityModule {
#ContributesAndroidInjector
fun provideMainActivity(): MainActivity
}
MainActivity.kt: Finally, extend from DaggerAppCompatActivity.
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
I believe this should run without issues. For more reference you could look into this sample and the new simpler docs at dagger.dev/android
You are missing the actual injection call.
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
MainActivity should extends DaggerActivity, not AppCompatActivity
I have problems when starting Dagger in Android project with Kotlin.
This estructure is the next one
Dagger is included in an Android module that is called by the client application
MagicBox.kt
interface MagicBox {
fun getDate(): Long?
}
MagicBoxImpl.kt
class MagicBoxImpl (): MagicBox{
var date: Long = Date().time
override fun getDate(): Long {
return date
}
}
MainModule.kt
#Module
class MainModule (private val app: Application) {
#Provides
#Singleton
fun provideMagicBox(): MagicBox {
return MagicBoxImpl()
}
}
MainComponent.kt
#Singleton
#Component(modules = [MainModule::class, PresenterModule::class])
interface MainComponent{
fun inject(target: Activity)
}
Application.kt
class Application: Application() {
lateinit var mainComponent: MainComponent
override fun onCreate() {
super.onCreate()
mainComponent = initDagger(this)
}
private fun initDagger(app: Application): MainComponent =
DaggerMainComponent.builder()
.mainModule(MainModule(app))
.build()
}
MainActivity.kt
#Inject
lateinit var magicBox: MagicBox
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
(application as ClientSdk).mainComponent.inject(this)
tvDaggerTest = findViewById(R.id.tvDaggerTest)
tvDaggerTest!!.text = magicBox.getDate().toString()
}
Get the following error
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property magicBox has not been initialized
fun inject(target: Activity) should be fun inject(target: MainActivity)
Also for better Dagger usage, the following should be:
#Module
abstract class MainModule {
#Binds
abstract fun magicBox(impl: MagicBoxImpl): MagicBox
}
and
#Singleton class MagicBoxImpl #Inject constructor(): MagicBox {
I am currently learning how to inject into Android apps with Dagger 2. I wrote a very basic code, but it refuses to work. My goals is it to inject the MainActicity as it should be. It builds
My code:
class MainActivity : AppCompatActivity() {
#Inject lateinit var info: Info
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
txt_view.text = info.textInformation
}
}
class Info {val textInformation = "You are able to read this" }
#Module
class InfoModule{
#Provides
fun info ():Info{
return Info()
}
}
class CustomApp : Application (),HasActivityInjector{
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
#Component(modules = arrayOf(AndroidInjectionModule::class,
ActivityModule::class))
interface ApplicationComponent{
fun inject(application: CustomApp)
}
#Module
abstract class ActivityModule{
#ContributesAndroidInjector(modules = arrayOf(InfoModule::class))
abstract fun contributeInfoActivityInjector():MainActivity
}
The answer is that I forgot to add this code to the CustomApp class, and .CustomApp to the manifest file
override fun onCreate() {
super.onCreate()
initDi()
}
private fun initDi() {
DaggerApplicationComponent.builder().build().inject(this)
}
I'm a Dagger new and I get the following error using it.
FATAL EXCEPTION: main
Process: com.biolabsalta.app, PID: 8028
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.lab.app/com.lab.app.ui.login.LoginActivity}:
kotlin.UninitializedPropertyAccessException: lateinit property
component has not been initialized
This error happened when I passed my project from java to kotlin.
Sharing my code. Please help
ActivityComponent.kt
#PerActivity
#Component(dependencies = arrayOf(ApplicationComponent::class),
modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
#ActivityContext
fun context(): Context
fun inject(activity: MainActivity)
fun inject(activity: LoginActivity)
...
}
ActivityModule.kt
#Module
class ActivityModule constructor(private val activity: AppCompatActivity) {
#Provides
#ActivityContext
fun provideContext(): Context = activity
#Provides
fun provideActivity(): AppCompatActivity = activity
#Provides
#PerActivity
fun provideMainPresenter(
presenter: MainPresenter<MainMvpView>): MainMvpPresenter<MainMvpView> = presenter
#Provides
#PerActivity
fun providerLoginPresenter(
presenter: LoginPresenter<LoginMvpView>): LoginMvpPresenter<LoginMvpView> = presenter
...
}
BaseActivity.kt
abstract class BaseActivity : AppCompatActivity(), MvpView {
lateinit var activityComponent: ActivityComponent
override val isNetworkConnected: Boolean
get() = NetworkUtils.isNetworkConnected(applicationContext)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent = DaggerActivityComponent.builder()
.activityModule(ActivityModule(this))
.applicationComponent(LabApp.component)
.build()
}
LoginActivity.kt
class LoginActivity : BaseActivity(), LoginMvpView {
#Inject
lateinit var presenter: LoginMvpPresenter<LoginMvpView>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
activityComponent.inject(this)
presenter.onAttach(this)
btn_login.setOnClickListener{
presenter.onServerLoginClick(account_email!!.text.toString(),
account_password.text.toString())
hideKeyboard()
}
LabApp.kt
class LabApp : Application() {
companion object {
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
component.inject(this)
}
}
Can anyone help me?
Thanks in advance.
UPDATE
I just forgot to declare in the manifest.
android:name=".LabApp"
I don't know anything about dagger but
You are calling
presenter.onAttach(this)
When LoginMvpPresenter<T> is not yet initialized.
#Inject
lateinit var presenter: LoginMvpPresenter<LoginMvpView>