Reputation: 2852
I recently started learning Hilt, and for now it's giving me more headaches than happiness, but I like challenges, so let's go to the point.
I'm having plenty of problems understanding how to inject in non activity classes, for example to inject a Context or any other class.
Most of my non activity classes were objects (singleton) so far, but as I wasn't able to inject into an object I decided to convert them to classes.
I'm trying to inject with field injection, because if I use constructor injection with @AndroidEntryPoint it complains that class must inherit from AppCompatActivity or so...
Let's put a simple example of my issue.
My app is a multi-module app and I have a AWFile class in the "Common" module (accesible from all other modules [I'm talking about Gradle modules, not Hilt modules now]) that carries file operations, so to start testing injection with Hilt I've created a method doSomething and, in doSomething I'm trying to get access to appSettings, a class where I hold all app configuration that is injected through field injection, but even the app builds fine I always get null -an error in fact- in the fields (I'm not sure how to get the context also).
AWApplication:
@HiltAndroidApp
class AWApplication : MultiDexApplication() {
}
AWFile:
class AWFile @Inject constructor() {
@Inject
lateinit var app: AppSettings
@Inject
lateinit var context: Context
fun doSomething(){
var color = app.actionBarEndColor
var ctx = context
}
...
}
Dependencies (in app module):
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@Provides
@ApplicationContext
fun provideContext(@ApplicationContext appContext: Context): Context {
return appContext
}
}
Dependencies in "Common" project module:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@ApplicationContext
lateinit var appContext: Context
@Provides
@Singleton
fun bindsAppSettings(): AppSettings {
return AppSettings()
}
@Provides
@Singleton
fun bindsAWFile(): AWFile {
return AWFile()
}
}
MainActivity:
@AndroidEntryPoint
class MainActivity : FragmentActivity() {
@Inject
lateinit var awFile: AWFile
@Inject
lateinit var app: AppSettings
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE)
super.onCreate(savedInstanceState)
awFile.doSomething()
}
The error when debugging into AWFile > doSomething:
kotlin.UninitializedPropertyAccessException: lateinit property app has not been initialized (the same for context)
Edit 1: Trying to do constructor injection (instead of field injection) in AWFile as @CommonsWare suggested.
AWFile declaration:
class AWFile @Inject constructor(private var app: AppSettings, private var context: Context)
"Common" module > Dependencies:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@ApplicationContext
lateinit var appContext: Context
@Provides
@Singleton
fun bindsAppSettings(): AppSettings {
return AppSettings()
}
@Provides
@Singleton
fun bindsAWFile(): AWFile {
return AWFile()
}
}
Result:
The application does not builds. It complains that not parameter was passed for app in bindsAWFile() > AWFile (the method I use to provide AWFile) and I don't know how to pass it :s
Edit 2: Still following @CommonsWare suggestions.
Dependencies:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@ApplicationContext
lateinit var appContext: Context
@Provides
@Singleton
fun bindsAppSettings(): AppSettings {
return AppSettings()
}
@Provides
@Singleton
fun bindsAWFile(appSettings: AppSettings): AWFile {
return AWFile(appSettings, appContext)
}
}
Result:
...Caused by: kotlin.UninitializedPropertyAccessException: lateinit property appContext has not been initialized
at dependencies.Dependencies.getAppContext(Dependencies.kt:18)...
Edit 3: Resolved.
App Dependencies class:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@Provides
@Singleton
fun provideContext(@ApplicationContext appContext: Context): Context {
return appContext
}
}
"Commons" module Dependencies class:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@Provides
@Singleton
fun bindsAppSettings(): AppSettings {
return AppSettings()
}
@Provides
@Singleton
fun bindsAWFile(appSettings: AppSettings, context: Context): AWFile {
return AWFile(appSettings, context)
}
}
Now it's working, and the keys were first to provide a Context in app Dependencies module and second to include appSettings and context into bindsAWFile method declaration as parameters that then will be passed to AWFile as @CommonsWare suggested.
Hilt is being a nightmare, but thanks for your time @CommonsWare.
Upvotes: 2
Views: 3766
Reputation: 2852
Ok, after struggling my head for hours I got a solution.
App Dependencies class:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@Provides
@Singleton
fun provideContext(@ApplicationContext appContext: Context): Context {
return appContext
}
}
"Commons" module Dependencies class:
@Module
@InstallIn(SingletonComponent::class)
class Dependencies {
@Provides
@Singleton
fun bindsAppSettings(): AppSettings {
return AppSettings()
}
@Provides
@Singleton
fun bindsAWFile(appSettings: AppSettings, context: Context): AWFile {
return AWFile(appSettings, context)
}
}
The rest of code keeps intact.
Now it's working, and the keys were first to provide a Context in app Dependencies module and second to include AppSettings and context into bindsAWFile method declaration as parameters that then will be passed to AWFile as @CommonsWare suggested.
Notice the important of the @ApplicationContext.
Upvotes: 1