lateinit property dispatchingAndroidInjector has not been initialized - android

I receive the above error "lateinit property dispatchingAndroidInjector has not been initialized " when I run my fragment in dagger2 .
The above error is triggered in my application class which is below
KotlinTemplateApplication.kt
class KotlinTemplateApplication:Application(), HasActivityInjector {
lateinit var retroComponent:RetroComponent
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
companion object {
#get:Synchronized
lateinit var instance: KotlinTemplateApplication
private set
}
override fun onCreate() {
super.onCreate()
instance = this
retroComponent = DaggerRetroComponent.builder().retroModule(RetroModule(APIURL.BASE_URL)).build()
//retroComponent.inject()
}
fun fetchRetroComponent():RetroComponent{
return retroComponent
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
My Fragment class is as below :
I called dagger related code in onAttach() method of fragment :
RetroDIFragment:
class RetroDIFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
lateinit var retroDIListViewModel: RetroDIListViewModel
lateinit var retroFitDIView: View
#Inject
lateinit var apiService: APIService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
retroDIListViewModel = ViewModelProviders.of(activity!!).get(RetroDIListViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
retroFitDIView = inflater.inflate(R.layout.fragment_retro_di, container, false)
return retroFitDIView
}
override fun onAttach(context: Context?) {
super.onAttach(context)
AndroidInjection.inject(activity)
KotlinTemplateApplication.instance.fetchRetroComponent().inject(this#RetroDIFragment)
retroDIListViewModel.fetchPostsFromWebSevice(apiService).observe(this,object : Observer<List<RetroModel>>{
override fun onChanged(t: List<RetroModel>?) {
for (i in t!!.indices)
println(t[i].id)
}
})
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment RetroDIFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
RetroDIFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
My component is as below :
RetroComponent :
#Singleton
#Component(modules = arrayOf(RetroModule::class))
interface RetroComponent {
fun inject(retroDIFragment: RetroDIFragment)
}
My Module is as below:
#Module
public class RetroModule(var urlPath:String) {
init{
this.urlPath = urlPath
}
#Singleton
#Provides
fun provideServiceAPI(retrofit: Retrofit):APIService{
return retrofit.create(APIService::class.java)
}
#Singleton
#Provides
fun provideRetrofit():Retrofit{
val retrofit = Retrofit.Builder()
.baseUrl(urlPath)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(providesOkHttpClientBuilder())
.build()
return retrofit
}
private fun providesOkHttpClientBuilder(): OkHttpClient {
val httpClient = OkHttpClient.Builder()
return httpClient.readTimeout(1200, TimeUnit.SECONDS)
.connectTimeout(1200, TimeUnit.SECONDS).build()
}
}
My Activity is as below
class RetroFitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_retro_fit)
supportFragmentManager.beginTransaction().replace(R.id.container_retro_di, RetroDIFragment()).commit()
}
}
I included below code in my Gradle:
implementation 'com.google.dagger:dagger:2.19'
implementation 'com.google.dagger:dagger-android:2.19'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.19'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
kapt 'com.google.dagger:dagger-android-processor:2.19'
kapt 'com.google.dagger:dagger-compiler:2.19'
//moxy
compile 'com.arello-mobile:moxy-app-compat:1.1.1'
kapt 'com.arello-mobile:moxy-compiler:1.1.1'
Can anyone help me in fixing this issue.

You need to change the AppComponent class inject method parameter:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
AppModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: Application) // This is the piece of code you need to change
}
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
AppModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: YourCustomAppClass) // Change to your custom app class
}
Also, where you are doing this
DaggerAppComponent
.builder()
.application(yourAppInstance)
.build()
.inject(yourAppInstance)
yourAppInstance needs to be of type YourCustomApp class instead of Application.

The dispatchingAndroidInjector property has to be set eventually.
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
As it's annotated with #Inject, it seems like you wanted to inject it. But since KotlinTemplateApplication is an Application, you need to do this manually on your component:
retroComponent.inject(this#KotlinTemplateApplication)

To use Dagger in a Fragment, you must add a DispatchingAndroidInjector <Fragment> in KotlinTemplateApplication
Edit KotlinTemplateApplication
class KotlinTemplateApplication : Application() , HasActivityInjector, HasSupportFragmentInjector {
#Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
#Inject lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate() {
super.onCreate()
........
DaggerAppComponent.builder().application(this).build().inject(this)
.........
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
override fun supportFragmentInjector(): AndroidInjector<Fragment> = fragmentInjector
}
You can also create a special module for Fragments and then add it to interface RetroComponent
#Component(modules = arrayOf(RetroModule::class,FragmentModule::class)
#Module
abstract class FragmentModule {
#ContributesAndroidInjector
internal abstract fun contributeRetroDIFragment(): RetroDIFragment
}
Then in your Fragment RetroDIFragment
class RetroDIFragment : Fragment() {
......
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
retroFitDIView = inflater.inflate(R.layout.fragment_retro_di, container, false)
return retroFitDIView
}
.........
/*---------------- Dagger Injection for Fragment -------------*/
#Override
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context);
}
}

this might be too late but this worked for me...
#set:Inject
internal var activityDispatchingAndroidInjector:DispatchingAndroidInjector<Activity>? = null
remove lateinit and use internal with #set:Inject instead of #Inject
this worked like charm for me.

I had the same issues with Dagger2.26v,
to fix ,
make sure ApplicationComponent extends AndroidInjector<SampleApplication> like
this:
#Singleton
#Component(modules = AndroidInjectionModule::class)
interface ApplicationComponent : AndroidInjector<SampleApplication> { …}

Related

dagger2 does not provide injection in base activity

I am trying to implement the latest version of Dagger2 in a Single-Activity app, but it is not known why, when initializing my starting activity, Dagger2 does not inject dependencies, I has a
fatal error in my base activity : Unable to resume activity kotlin.UninitializedPropertyAccessException: lateinit property navigatorHolder has not been initialized
here is my code
AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class,
ActivityProviderModule::class,
AndroidInjectionModule::class,
NetworkModule::class,
RemoteModule::class,
NavigationModule::class,
ParserModule::class,
CacheModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun context(context: Context) : Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
App:
class App : DaggerApplication(){
private val applicationInjector =
DaggerAppComponent.builder().application(this).context(this).build()
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
applicationInjector
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
AppActivity:
class AppActivity : MvpAppCompatActivity() , HasAndroidInjector, RouterProvider {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
#Inject
lateinit var mainActivityProvider: ActivityProvider
#Inject
lateinit var navigatorHolder: Lazy<NavigatorHolder>
#Inject
override lateinit var ciceroneRouter: Router
override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector
private val navigator : Navigator by lazy {
CustomSupportAppNavigator(this, supportFragmentManager, R.layout.activity_main)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
AndroidInjection.inject(this)
mainActivityProvider.acitvity = this
Timber.e("onCreate AppActivity")
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
initBottomBar()
}
override fun onResumeFragments() {
super.onResumeFragments()
navigatorHolder.get().setNavigator(navigator)
}
ActivityInjectionModule
#Module(includes = [AndroidInjectionModule::class])
interface ActivityInjectionModule {
#ContributesAndroidInjector(
modules = [FragmentInjectionModule::class]
)
fun activityInjector() : AppActivity
}
NavigationModule
#Module
class NavigationModule {
#Provides
#Singleton
fun provideRouter() = App.cicerone.router
#Provides
#Singleton
fun provideNavigatorHolder() : NavigatorHolder {
return App.cicerone.navigatorHolder
}
#Provides
#Singleton
fun provideLocalNavigationHolder(): LocalCiceroneHolder {
return LocalCiceroneHolder()
}
}
Also i post android:name=".App" inandroid manifest.
I have tried many different options already, but I still cannot find the reason
Can you change your App to like this and try again:
class App : DaggerApplication(){
private val appComponent =
DaggerAppComponent.builder().application(this).context(this).build().inject(this)
companion object {
lateinit var cicerone: Cicerone<Router>
private set
}
override fun onCreate() {
super.onCreate()
cicerone = Cicerone.create()
initAppComponent()
initStetho()
Timber.plant(Timber.DebugTree())
}
Also remove AndroidInjector<App> from AppComponent

How to use pass the activity to an dagger module using the new `dagger-android`

Android Studio 3.4
dagger-android 2.21
How to use pass the activity to an dagger module using the new dagger-android
Before using the older version of dagger we could pass the Activity in the constructor and return that in the provider method. But I not sure how to do that with dagger-android
I have the following module. However, dagger doesn't now about the ForecastActivity.
#Module
class ActivityModule {
#Reusable
#Provides
fun provideRetryListener(forecastActivity: ForecastActivity): RetryListener {
return forecastActivity
}
}
The RetryListener is a interface that the ForecastActivity implements. I want to be able to inject this RetryListener into my RetryFragment i.e.
class RetryFragment : Fragment() {
#Inject
lateinit var retryListener: RetryListener // Inject here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
AndroidSupportInjection.inject(this)
super.onCreateView(inflater, container, savedInstanceState)
retryListener.onRetry() // usage like this
return inflater.inflate(R.layout.failurecase_layout, container, false)
}
}
In the forecastActivity
class ForecastActivity : AppCompatActivity(), RetryListener {
#Inject
lateinit var forecastPresenter: ForecastPresenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
}
override fun onRetry() {
/* do something here */
}
}
My ActivityBuilder is the following:
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [ActivityModule::class])
abstract fun injectIntoRetryFragment(): RetryFragment
}
My component is this:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
ActivityModule::class])
interface StockComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: StockApplication): Builder
fun build(): StockComponent
}
fun inject(application: StockApplication)
}
And my Application is:
class StockApplication : Application(), HasActivityInjector, HasSupportFragmentInjector {
#Inject
lateinit var dispatchingAndroidActivityInjector: DispatchingAndroidInjector<Activity>
#Inject
lateinit var dispatchingAndroidFragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate() {
super.onCreate()
DaggerStockComponent
.builder()
.application(this)
.build()
.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidActivityInjector
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidFragmentInjector
}
}
So the question is when using dagger-android how can I inject the RetryListener into the RetryFragment that the RetryListener is implemented by the ForecastActivity?
Many thanks in advance
I can't see a module that provides your ForecastActivity, i.e.:
#ContributesAndroidInjector
abstract fun forecastActivity(): ForecastActivity
The Activity will need to implement HasSupportFragmentInjector, and supply a DispatchingAndroidInjector<Fragment>

Wrong graph in Dagger 2 with kotlin

I am trying to use Dagger 2 with Kotlin and I am missing something. The problem comes when I try to inject an MVP presenter into the Fragment.
These are my files:
AppClass
class AppClass : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
companion object {
lateinit var instance: AppClass private set
}
}
AppComponent
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
fun inject(appClass: AppClass)
#Component.Builder
interface Builder {
#BindsInstance
fun application(appClass: AppClass): Builder
fun build(): AppComponent
}
}
AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app : AppClass) = app
#Provides
#Singleton
fun provideDatabaseManager() = DatabaseManager()
}
ActivityBuilder
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [ActivityModule::class, HomeFragmentProvider::class])
internal abstract fun bindHomeActivity(): HomeActivity
}
HomeFragmentProvider
#Module
public abstract class HomeFragmentProvider {
#ContributesAndroidInjector(modules = HomeFragmentModule.class)
abstract HomeFragment provideHomeFragmentFactory();
}
HomeFragmentModule
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(databaseManager: DatabaseManager): HomeContract.Presenter {
return HomePresenter(databaseManager)
}
}
HomeFragment
class HomeFragment : HomeContract.View {
#Inject lateinit var mPresenter: HomeContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
if (arguments != null) {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
return view
}
companion object {
private val STARTING_PAGE_INDEX = 0
fun newInstance(): HomeFragment {
val fragment = HomeFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}
Log
...app/build/tmp/kapt3/stubs/debug/.../di/component/FragmentComponent.java:9: error: [Dagger/MissingBinding] ....HomeContract.Presenter cannot be provided without an #Provides-annotated method.
I try to inject the DatabaseManager in the Activity and works fine so I suppose that my problem is related to the Fragment dependencies.
Any help is appreciated.
UPDATE
HomePresenter
class HomePresenter() :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
private lateinit var mDatabaseManager : DatabaseManager
#Inject constructor(databaseManager: DatabaseManager) : this() {
this.mDatabaseManager = databaseManager
}
}
Define HomePresenter variable as a function parameter, as below:
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(homePresenter: HomePresenter): HomeContract.Presenter {
return homePresenter
}
}
Modify your HomePresenter class as below:
class HomePresenter #Inject constructor(val databaseManager: DatabaseManager) :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
....
}
Also, you can use DatabaseManager instance from the constructor directly by marking it as val, there is no need to define it separately.
In AppComponent, try using AndroidInjectionModule::class if you are not using support Fragments.
#Singleton
#Component(modules = [AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
....
}
Hope this will resolve your issue!
I came up with the problem. If I change the injection in the HomeFragment from #Inject lateinit var mPresenter: HomeContract.Presenter to #Inject lateinit var mPresenter: HomePresenter everything works fine.

Inject property into ViewModel using Dagger 2

I try to learn how to use Dagger 2. Please help with follow exception:
Exception:
UninitializedPropertyAccessException: lateinit property trips has not
been initialized
MainActivityViewModel:
class MainActivityViewModel : ViewModel() {
private lateinit var tripsLiveData: MutableLiveData<List<Trip>>
#Inject
lateinit var trips : List<Trip>
fun getTrips() : LiveData<List<Trip>> {
if (!::tripsLiveData.isInitialized){
tripsLiveData = MutableLiveData()
tripsLiveData.value = trips
}
return tripsLiveData
}
}
TripModule:
#Module
class TripModule{
#Provides
fun provideTrips(): List<Trip> {
var list = ArrayList<Trip>()
list.add(Trip(100,10))
list.add(Trip(200,20))
return list
}
}
AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
TripModule::class])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
MainActivity:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var tripsAdapter: TripsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
val model = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
}
}
If you want your viewmodel's to be part of the dagger graph, you need to do several things - using dagger's multibindings (just once, for newer viewmodels it will be easier). You'd create new viewmodel factory which will take care of instantiating viewmodels. This factory will be part of dagger graph and therefore will have references to anything provided via dagger. You can then have either constructor injection via #Inject constructor(anyParameterFromDagger: Param) or #Inject lateinit var someParam: Param inside the body of viewmodel.
1) Create qualifier for view model classes
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
2) create viewmodel factory which takes values from dagger's multibindings
#Singleton
class DaggerViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class $modelClass")
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
3) have dagger module which will provide the factory (from point 2) and then your viewmodels
abstract class YourDaggerModuleWhichThenNeedToBePartOfYourGraphAsIncluded {
#Binds
abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory // this needs to be only one for whole app (therefore marked as `#Singleton`)
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
abstract fun bindMainActivityViewModel(vm: MainActivityViewModel): ViewModel // for every viewmodel you have in your app, you need to bind them to dagger
}
4) in your activity, when you get your viewmodel, you need to use the factory from dagger: (places changed marked as // TODO in the code below)
class MainActivity : AppCompatActivity() {
#Inject
lateinit var tripsAdapter: TripsAdapter
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory // TODO this was added to the activity
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
val model = ViewModelProviders.of(this, viewModelFactory)[MainActivityViewModel::class.java] // TODO this was changed
model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
}
}
I didn't provide the code for including module to dagger component as I hope this is something you already did.
You can read more about this e.g. in this medium article (I'm not author of the article):
#Provides
fun provideTrips(): List<Trip> {
I'm a bit wary of this in the sense that I doubt that it's really Dagger's job to provide this for you directly, but I'll just take that for granted and ignore that for now.
Your code should be:
class MainActivityViewModel #Inject constructor(
trips: List<Trip>
): ViewModel() {
private val tripsLiveData: MutableLiveData<List<Trip>> = MutableLiveData()
init {
tripsLiveData.setValue(trips)
}
fun getTrips() : LiveData<List<Trip>> = tripsLiveData
}
#Module
class TripModule{
#Provides
// #Singleton // <-- possibly should be here?
fun provideTrips(): List<Trip> = listOf(
Trip(100,10),
Trip(200,20)
)
}
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
TripModule::class
])
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
class MainActivity : AppCompatActivity() {
#Inject
lateinit var viewModelProvider: Provider<MainActivityViewModel>
// this is specifically not marked with `#Inject`
lateinit var viewModel: MainActivityViewModel
private val tripsAdapter = TripsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
// Inject external dependencies
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupRecyclerView();
setUpViewModel();
}
private fun setupRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(context)
adapter = tripsAdapter
}
}
private fun setUpViewModel(){
viewModel = ViewModelProviders.of(this, object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass == MainActivityViewModel::class.java) {
#Suppress("UNCHECKED_CAST")
return viewModelProvider.get() as T
}
throw IllegalArgumentException("Unexpected argument: $modelClass")
}
}).get(MainActivityViewModel::class.java)
viewModel.getTrips().observe(this, Observer {
val trips = it ?: return#observe
tripsAdapter.trips = trips
})
}
}
Aka you should use constructor injection, use the Provider<T> to only get the ViewModel from Dagger when you actually need it, and otherwise initialize it via a ViewModelProviders.Factory so that you actually get the ViewModel from Dagger.

Dagger2 + Kotlin: lateinit property has not been initialized

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.

Categories

Resources