20

I am building an Android app following the Clean Architecture Principles. Here is what I've got:

app module:

  • Contains all the Android dependencies.
  • Uses MVVM with ViewModel from the arch components.
  • ViewModels only communicate with UseCases, which are constructor injected.

usecase module:

  • Contains all of the use cases.
  • Use cases only communicate with Repositories, which are constructor injected.

repository module:

  • Contains all of the repositories.
  • Repositories communicate with web services or database etc.
  • I have a Retrofit interface defined in this layer, which the repository takes in it's constructor.

data module:

  • Contains all the data models

I am trying to use Hilt for dependency injection in the app. I don't want to expose Retrofit, OkHttp etc to the app module because I don't want developers to be able to put network code in the wrong module. Remember, the app module uses ViewModel which can ONLY talk to use cases.

How do I set this up? I tried putting dagger modules in each of these modules to define injection, then in the main app module I included the module from usecase:

@Module(includes = [UseCaseModule::class])
@InstallIn(ApplicationComponent::class)
object AppModule

but this does not work as it starts to complain about not being able to find transitive dependencies in the other modules that I want to keep hidden.

Christopher Perry
  • 38,891
  • 43
  • 145
  • 187

3 Answers3

3

Unfortunately, Hilt uses a monolithic approach currently. This means that your app module will have access to ALL your modules.

I don't want to expose Retrofit, OkHttp etc. to the app module because I don't want developers to be able to put network code in the wrong module.

No, you don't have to include your network-related classes in the app module. Rather, in the data module.

But bear in mind that the app module will still indirectly have access to Retrofit by implementing the data module for Hilt to work. You can check out this post.

3

TLDR: Use Hilt version 2.40+ which allows working with transitive dependencies


Since Hilt version 2.37 there's an gradle flag enableAggregatingTask which allows Hilt to collect transitive dependencies. The flag is enabled by default in version 2.40 and newer.

When you have your gradle modules as :app -> :usecase -> :repository -> :retrofit (external) and assuming you use implementation in your gradle scripts, your Retrofit instance can still be provided and injected in :repository module, but it won't leak into other modules.

So in case you want to encapsulate your Retrofit in your repository module, you may create

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {

  @Provides
  fun provideRetrofit(retrofitBuilder: Retrofit.Builder): YourApi = retrofitBuilder.create<YourApi>()

}

You don't need to manually include your RepositoryModule in :app as this is done by Hilt and the @InstallIn annotation.

mlykotom
  • 4,850
  • 1
  • 22
  • 27
0

I've got the same approach with multi-module, but a bit more extense than yours and it works (app, core, navigation, api, data, domain, presentation, coreAndroidTest).

For the AppModule, you don't need to specify that includes UseCaseModule, just make sure you add @InstallIn ApplicationComponent:

@Module
@InstallIn(ApplicationComponent::class)
class AppModule {

    @Provides
    fun provideContext(app: Application): Context = app.applicationContext

    @Provides
    fun provideResources(app: Application): Resources = app.resources
}

The same way when you define your UseCaseModule in a different module:

@Module
@InstallIn(ApplicationComponent::class)
class DomainModule {
// Your @Provides
}
Dharman
  • 30,962
  • 25
  • 85
  • 135