I am trying to implement a clean code architecture similar to what's described here. To enforce it, each layer is a separate module in Android Studio with the dependency chain thus:
model(pure-kotlin) -> interactor(pure-kotlin) -> viewmodel(android) -> view(android)
So to be clear, the view
only knows about the viewmodel
and the viewmodel
only knows about the interactor
. The view (normally the app
module) is where the MainActivity
lives.
I am currently working on the interactor
in isolation and I am trying to use Dagger 2 for injection there. I have written the following code all in the interactor
module:
... (Build.gradle)
implementation 'com.google.dagger:dagger:2.20'
kapt 'com.google.dagger:dagger-compiler:2.20'
...
abstract class ApiServiceInteractor() {
@Inject lateinit var apiService: ApiService
@Inject lateinit var schedulers: InteractorSchedulers
}
class Posts @Inject constructor() : ApiServiceInteractor() {
fun fetch(): Single<List<PostSummary>> =
apiService.getPosts()
.subscribeOn(schedulers.io())
.toObservable()
.flatMapIterable { postList -> postList }
.map { post -> PostSummary(post.id, post.title) }
.toList()
}
@Module
open class InteractorModule {
@Singleton @Provides
open fun provideApiService(): ApiService = ApiService.create()
@Provides
open fun provideSchedulers(): InteractorSchedulers = InteractorSchedulers()
}
@Component
interface Component {
fun inject(posts: Posts)
}
val dagger = DaggerComponent.create() // <--- not generated!!!
The code does not compile. The compiler complains that the injected apiService
and schedulers
in ApiServiceInteractor
need a corresponding @Provides
, furthermore the DaggerComponent
implementation is not auto-generated. Now, I realise that the Component
interface doesn't make sense here, but you need one to auto-generate DaggerComponent
and this is just prototyping at this stage.
If I move the Component
interface and the DaggerComponent.create()
into the view (and have the inject signature use MainActivity
instead of Posts) then everything compiles fine and DaggerComponent
is created.
My worry is that when everything is finally written, Dagger will not be able to resolve the @Provides
for apiService
and schedulers
because there is a degree of separation between the view
and the interactor
(where the @Provides
are). The only examples I have seen that are anything similar to this have the @Provides
in the view
(app
) module. If I do that, however, I will break the clean code architecture dependency rule because the view
will then have a dependency on the interactor
.
So will it work the way it is? Will those values get injected?
UPDATE
I removed the '@Component' and 'DaggerComponent' declarations from view
, put them back in interactor
and added '@Component(modules = [InteractorModule::class])'. The compiler no longer complains about missing provides (thanks Ivan), but the IDE still reports that DaggerComponent does not exist. Calling DaggerComponent.create().inject(this)
in Posts init does not compile because DaggerComponent has not been generated.
So it appears that DaggerComponent
can only be generated in the view
module which means that the @Component
declaration also has to be in the view
module, along with the modules = [InteractorModule::class]
annotation parameter. And once you do that, you have a dependency between view
and interactor
on InteractorModule
, breaking the clean code architecture dependency rule.
So the main question still remains, how to get Dagger 2 to work in a pure-Kotlin module removed from the Android layer?