Dagger hilt injecting into activity results in UninitializedPropertyAccessException error - android

I am trying to inject a class into an activity using dagger hilt using a Module. I've looked through tutorials and countless SO posts. I cannot tell what I'm doing wrong.
I have a DataStoreManger class that i'm trying to use in an activity.
class DataStoreManager (#ApplicationContext appContext: Context) {...}
I have an AppModule that provides a DataStoreManager.
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideDataStoreManager(#ApplicationContext appContext: Context):
DataStoreManager = DataStoreManager(appContext)
}
Then I'm trying to use DataStoreManager in MainActivity.
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var dataStoreManager: DataStoreManager
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
}
This results in an uninitialized property access exception
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property
dataStoreManager has not been initialized

When you use
#Inject lateinit var dataStoreManager
The dataStoreManager is only actually injected during the call to super.onCreate().
However, when you create a property such as
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
This property is created immediately as part of the construction of your MainActivity class - it doesn't wait until onCreate() runs, hence the error you are receiving.
Generally, the easiest way to avoid this is by only access the userPreferencesFlow from your injected manager when you actually want to collect it, which is presumably after super.onCreate().
However, if you want to still create your flow as a member variable at the activity level, you can use by lazy:
private val userPreferencesFlow by lazy {
dataStoreManager.userPreferencesFlow
}
The lazy block will only be executed lazily - e.g., the first time you actually use the member variable, thus ensuring that your #Inject variable has actually been injected.

Related

When do I need to add annotation when I use Hilt as dependency injection?

I'm reading the article Using Hilt in your Android app.
The Code A, Code B and Code C are from the sample project in solution branch.
I'm confused when I should add the Hilt annotation.
In Code A, the parameter database of fun provideLogDao is injected, but the author doesn't add any annotation, but the parameter appContext of fun provideDatabase is marked with annotation #ApplicationContext, why ?
In Code B, the parameter logDao of class LoggerLocalDataSource is injected, but the author doesn't add any annotation, why?
In Code C, I'm told the following content, why isn't the parameter activity of class AppNavigatorImpl added any annotation? and you know ApplicationContext is predefined binding too, but the author add annotation #ApplicationContext in fun provideDatabase in Code A.
Because an AppNavigator instance is provided in the Activity container , FragmentActivity is already available as a predefined binding.
Code A
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
class LoggerLocalDataSource #Inject constructor(private val logDao: LogDao) : LoggerDataSource {
...
}
Code C
class AppNavigatorImpl #Inject constructor(private val activity: FragmentActivity) : AppNavigator {
...
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigator
...
}
#ApplicationContext "tells" hilt to inject the application context, and not activity or service contexts. Also reminds us that injecting it will not cause memory leak.
#Inject is right there, at the left of the constructor. In DI terms, is called "Constructor Injection", which means that all of the parameters of the constructor will be injected by hilt without annotating those one by one.
Singleton component's object graph is not being garbage collected until your app is killed - injecting an activity there will cause memory leak.

In Hilt, How can I Inject a context in a non activity/ fragment class using field injection?

I have a Exception Handling class
class IOException : BaseException {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface AnalyticsServiceProviderEntryPoint {
fun analyticsService(): AnalyticsService
}
private val hiltEntryPoint = EntryPointAccessors.fromApplication(**Need Context**, AnalyticsServiceProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
}
If I see this offical link, it says
In this example, you must use the ApplicationContext to retrieve the
entry point because the entry point is installed in SingletonComponent
What If I don't have the context in the class and in the function body which I will use and I don't want to use from Constructor Injection as well?
I only want to use the field injection. How can I access it, since I don't have the context.
In your application add a companion object with lateinit var applicationContext and initialize the variable when the application is initialized like:
companion object {
lateinit var applicationContext: Context
private set
then you have an static variable with the application context and you can you something like:
private val hiltEntryPoint = EntryPointAccessors.fromApplication(Application.applicationContext, AnalyticsServiceProviderEntryPoint::class.java)
I can't think of anything else to do what you want to do
Did you try to get applicationContext from dagger-hilt ApplicationContextModule? Its already provided by this module in your app and you can get that probably. You need to use the qualifer also
#Provides
#ApplicationContext
Context provideContext() {
return applicationContext;
}

Provide Activity instance with Hilt

How can I translate something like this:
#Module
abstract class BaseActivityModule<A : AppCompatActivity> {
#Binds
abstract fun provideActivity(activity: A): AppCompatActivity
companion object {
#Provides
#ActivityContext
fun provideContext(activity: AppCompatActivity): Context = activity
}
}
#Module
abstract class SomeActivityModule : BaseActivityModule<SomeActivity>()
So it can be used latter like:
#ActivityScope
class UtilsClass #Inject constructor(
private val activity: AppCompatActivity,
...
){...}
I've migrated a playground project from dagger to hilt and it went super smooth, but I've stumbled across this use-case. I've changed the code so I wont be needing that instance any more but the curiosity remains.
Is it even possible now that we don't need this kind of setup:
#ActivityScope
#ContributesAndroidInjector(modules = [SomeActivityModule::class])
abstract fun someActivity(): SomeActivity
i didn't try this code yet, if its not working please CMiMW,
according to documentation here, you can use predefined qualifers for Application Context and activity context.
your code may look like this
#ActivityScoped
class UtilsClass #Inject constructor(
#ActivityContext private val activity: Context,
...
){
...
val myActivity = if(context is MyActivity) context as MyActivity else throw ...... // check if its provided context was desired activity
...
}
Yes, you can cast #ActivityContext to that of your activity, read on for more clarification.#ActivityContext can be used to scope the bindings that require activity context.
But similar to dagger scoping scheme, you can only scope in classes that are #ActivityScoped, i.e if you try #ActivityContext in a class and scope it with any other scope wider than #ActivityScoped, you will face compile time error
#dagger.hilt.android.qualifiers.ActivityContext
android.content.Context cannot be provided without an
#Provides-annotated method
Moreover, new bindings objects will instantiated every time any new activity is created.
refer https://developer.android.com/training/dependency-injection/hilt-android#component-scopes
Just cast/get activity from context. But you can also specify class according to the docs
tailrec fun Context?.activity(): Activity? = this as? Activity
?: (this as? ContextWrapper)?.baseContext?.activity()
class MyClass #Inject constructor(#ActivityContext context: Context) {
init {
val activity = context.activity() as Activity
}
//...
}
Yes, We don't need this kind of setup. We just have to provide #AndroidEntryPoint on our activities and Hilt will handle the dependency injections on its own. Also with Hilt, There is no need to write Factory and InjectorComponents.
I was facing situation while working on project, thought I would share if this helps someone.
The activity Context binding is available using #ActivityContext
class PaymentService #Inject constructor(
#ActivityContext context: Context
) { ... }
and Activity binding is available without qualifiers.
class PaymentService #Inject constructor(
activity: FragmentActivity
) { ... }

Unable to inject instance variable using dagger

I am creating the new class manually without injecting through dagger, but all the instances variable in that class should be injected through dagger.
For Example:
class TestingClass constructor() {
#Inject
lateinit var test: Test
fun testing() {
test.doSomeThing()
}
}
class Test #Inject constructor() {
fun doSomeThing() {
}
}
TestingClass().testing()
I am trying to achieve the above scenario, but it throws error
kotlin.UninitializedPropertyAccessException: lateinit property test has not been initialized
Is it possible to achieve this scenario, Can anyone help me with this?

Dagger can not provide injection with Kotlin

I have this issue when I try to use Kotlin and Dagger 2 .
"interface cannot be provided without an #Provides- or #Produces-annotated method.”
This is my Module class:
#Module
class MenuActivityModule(#NonNull private val menuActivity: MenuActivity) {
#Provides
#MenuActivityScope
fun provideGameScreenDimensions(application: Application) =
GameScreenDimension(application.resources)
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
#Provides
#MenuActivityScope
fun providePowerManager() =
menuActivity.getSystemService(Context.POWER_SERVICE) as PowerManager
#Provides
#MenuActivityScope
fun provideWakeLock(#NonNull powerManager: PowerManager) =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Preferences.APPLICATION_TAG)
}
This is a part of my Activity class, where I inject some variables with Dagger:
class MenuActivity : BaseActivity {
#Inject
lateinit var myAudio: Audio
#Inject
lateinit var wakeLock: PowerManager.WakeLock
#Inject
lateinit var apiService : ApiService
#Inject
lateinit var sharedPref : SharedPreferences
#Inject
lateinit var gameDimension : GameScreenDimension
init {
DaggerMenuActivityComponent.builder()
.menuActivityModule(MenuActivityModule(this))
.build()
.inject(this)
}
//more code
}
Audio.kt is interface and Dagger has problem to inject it. Inside the activity module I am returning AndroidAudio
instance, which implements Audio interface. I am not sure what is the problem here. In Java I have had many times injection of interfaces and I never had this issue before.
If somebody can help me I will be so happy.
Thanks!
I think the solution for your problem is very simple and also not so obvious unfortunately.
Because Kotlin does not require type to be specified on methods return, you can easily write something like this:
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
And the compiler will not complain about that, but in this case Dagger will provide AndroidAudio object for injection. In you Activity you are looking for Audio object for injection. So if you change this code to be:
#Provides
#MenuActivityScope
fun provideAudio(): Audio =
AndroidAudio(menuActivity)
Everything should be ОК.
Give a try and tell me if something does not work.
Thanks.
BTW : When I use Dagger with Kotlin I aways specify the type of returned value, because usually that is gonna be the type of the injected variables or the type of the variable which you are going to use in your dagger module.

Categories

Resources