Reputation: 313
Recently I started to divide our application into smaller Android modules, but I'm having a hard time to make Dagger work the way I want it to.
My current dagger setup consist of:
- ApplicationComponent
marked with @Singleton
. This component is created on app start.
- UserSubComponent
marked with @UserScope
. This subcomponent is created when the user login.
Both these components is placed in my app
module together with my App
class who's responsible for creating both components.
In my login
module (Which is a parent to my app module, so it can't access anything in the app module) I have my AuthenticationManager
.
When the user login I use RxJava to signal from my AuthenticationManager
to App
, so the UserSubComponent
can be created.
My problem is that I need to access some dependencies from my UserSubComponent
, after it have been created, in my AuthenticationManager
so I can preload the user's data before moving on.
Module structure:
app (AppComponent & UserSubComponent)
^
|
login (AuthenticationManager) - feature 2 - feature 3
My App class:
class App : DaggerApplication() {
@Inject
lateinit var authenticationManager: AuthenticationManager
override fun onCreate() {
super.onCreate()
authenticationManager
.authenticationStateStream
.subscribe { state ->
if (state == AuthenticationState.AUTHENTICATED) {
AppInjector.userComponent.inject(this)
}
}
}
AuthenticationManager:
class AuthenticationManager @Inject constructor(loginApi: LoginApi) {
@Inject
lateinit var preLoader : PreLoader // This won't work because of different scope
val authenticationStateStream = Observable<AuthenticationState>()
fun login() {
if (success) {
authenticationStateStream.emit(AuthenticationState.AUTHENTICATED)
// UserSubComponent is now created
preLoader.preload()
}
}
}
App component
@Singleton
@Component(modules = [AppModule::class, AndroidSupportInjectionModule::class])
interface AppComponent : AndroidInjector<App> {
fun userComponentBuilder(): UserComponent.Builder
}
AppModule
@Module
class AppModule {
@Provides
@Singleton
fun provideLoginApi() = LoginApi()
}
UserSubComponent
@UserScope
@Subcomponent(modules = [UserModule::class, AndroidSupportInjectionModule::class])
interface UserComponent : AndroidInjector<App> {
@Subcomponent.Builder
interface Builder {
fun build(): UserComponent
}
}
UserModule
@Module
class UserModule {
@Provides
@UserScope
fun providesPreLoader() = PreLoader()
}
Can I somehow get this structure to work? Or what are my options when it comes to modules + dagger?
Upvotes: 10
Views: 2354
Reputation: 313
So after a lot of trying, I finally managed to solve my problem.
What I discovered was:
I found this article really helpful: https://proandroiddev.com/using-dagger-in-a-multi-module-project-1e6af8f06ffc
Upvotes: 2
Reputation: 3702
When Dagger tries to create an object of AuthenticationManager
, it will also try to inject any @Inject
annotated fields as well. And that doesn't work as you correctly pointed out that PreLoader
is from a different scope which essentially means that it is from a different component, i.e., UserComponent
.
Important thing to note here is that UserComponent
is not yet created at the point AuthenticationManager
object is first created by Dagger. And since PreLoader
is provided byUserComponent
, there is no way Dagger could inject that field. However you can inject that field into AuthenticationManager
by yourself.
Something like this:
class AuthenticationManager @Inject constructor(loginApi: LoginApi) {
// Remove the @Inject annotation and provide a custom setter which will pre-load user data
private lateinit var preLoader : PreLoader
set(preLoader) {
field = preLoader
preLoader.preload()
}
val authenticationStateStream = Observable<AuthenticationState>()
fun login() {
if (success) {
authenticationStateStream.emit(AuthenticationState.AUTHENTICATED)
// UserSubComponent is now created
// You can't do this here:
// preLoader.preload()
}
}
}
Your App class:
class App : DaggerApplication() {
@Inject
lateinit var authenticationManager: AuthenticationManager
override fun onCreate() {
super.onCreate()
authenticationManager
.authenticationStateStream
.subscribe { state ->
if (state == AuthenticationState.AUTHENTICATED) {
// Here you can directly set the PreLoader object yourself
authenticationManager.preLoader =
AppInjector.userComponent.preLoader()
}
}
}
}
In order to access PreLoader
object, you also need to modify your UserComponent
like this:
@UserScope
@Subcomponent(modules = [UserModule::class, AndroidSupportInjectionModule::class])
interface UserComponent : AndroidInjector<App> {
fun preLoader() : PreLoader // <-- Need to add this
@Subcomponent.Builder
interface Builder {
fun build(): UserComponent
}
}
Upvotes: 1