Dagger 2 to Hilt in Multi Module App

June 10, 2022

Dagger

Dagger is the most popular dependency injection, especially in Android development. Previously Dagger 2 was the option when building an App, but now there is a new dependency injection called Dagger Hilt that has a lot of improvement from it predecessor (Dagger 2).

Multi Module App

Nowadays in the Android development world, big companies are likely to make their app a multi module app, because it has a lot of benefits like the build speed, dynamic delivery module, ect. so Dagger (espesially Hilt) must be able to manage a dependency for this kind of features

But what kind of improvement does hilt has? How can we use Hilt in a multi module or dynamic feature module (DFM) app?

So here we will learn about improvement that Hilt has over Dagger 2, also how to implement Hilt in DFM App 🙌

⚠️ In this article we are not showing how to add the library, and the step2 for creating dagger dependency, you can learn it by looking at this Repository movieapp as it will be the example of the app we use in this article

Hilt improvement from Dagger 2

As we know Hilt is the successor of the Dagger 2, that’s why Hilt have a lot of improvement, like examples below

1. No Component Interface in Hilt
In dagger hilt we don’t have to create the Component Interface as our dagger entry point, We just have to add @AndroidEntryPoint

Dagger 2

@AppScope
@Component(
dependencies = [CoreComponent::class],
modules = [AppModule::class, ViewModelModule::class]
)
interface AppComponent {
@Component.Factory
interface Factory {
fun create(coreComponent: CoreComponent): AppComponent
}
fun inject(activity: MainActivity)
fun inject(fragment: HomeFragment)
}
----------------------------------------------------------------
(requireActivity().applicationContext as App).appComponent.inject(this)

Hilt

@HiltAndroidApp
open class App: Application()
@AndroidEntryPoint
class HomeFragment: Fragment()
@AndroidEntryPoint
class MainActivity : AppCompatActivity()

For Dagger 2 We have to create AppComponent as it will be the entry point for dagger, and inject the dependency in Activity and Fragment class

In Hilt we just have to add @HiltAndroidApp for Application class and @AndroidEntryPoint for Activity and Fragment class. then we already make the component entry point for dagger

2. Hilt provide application context
For Dagger 2 we still need to inject the context by injecting it in the view, but for hilt we just have to use the annotation of @ApplicationContext to provide the application context

Dagger 2

@Singleton
@Provides
fun provideDatabase(context: Context): TMDBDataBase = Room.databaseBuilder(
context,
TMDBDataBase::class.java, "TMDBDataBase.db"
)
--------------------------------------------------------------------------------
(requireActivity().applicationContext as App).appComponent.inject(applicationContext)

Hilt

@Singleton
@Provides
fun provideDatabase(@ApplicationContext context: Context): TMDBDataBase = Room.databaseBuilder(
context,
TMDBDataBase::class.java, "TMDBDataBase.db"
)

3. Hilt has it standardize scope
Hilt has standardize the component scope without us need to write it
(more info: hilt components)

Dagger 2

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class AppScope
--------------------------------------------------------------------------------
@AppScope
@Component(
dependencies = [CoreComponent::class],
modules = [AppModule::class, ViewModelModule::class]
)
interface AppComponent {
@Component.Factory
interface Factory {
fun create(coreComponent: CoreComponent): AppComponent
}
fun inject(activity: MainActivity)
fun inject(fragment: HomeFragment)
}

Hilt

@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
@Binds
abstract fun provideMovieUseCase(movieInteractor: MovieInteractor): MovieUseCase
}

In Dagger 2 we need to create our own scope (example: @AppScope) to set the lifecycle of our component

In Hilt there are already standardized components that work on its own lifecycle like SingletonComponent, ViewModelComponent. ActivityComponent, FragmentComponent, ect.

Yes that correct, for MVVM architecture, Hilt support ViewModel Scope so we don’t have to bind the ViewModel like when we use the dagger 2

Hilt In Dynamic Feature App

So for all knowledge we have above, it just works when our app is a single module app or multi module app with a standard module. But In the DFM app, Hilt can’t obtain the dependency, because Hilt uses a monolithic component system which is incompatible with DFM that loads classes dynamically. so for a DFM app we have to use the old dagger way with the help of @EntryPoint annotation. Yes after all of this you should learn dagger 2 as well 😁

but we can still use the benefit of hilt component scope, so we don’t have to create the scope our self 🤩

Dynamic Feature App Structure

So for better understanding we will use this structure of application where there are one base module that is :app, two dynamic feature module of :moviedetail and :favmovie, and one module that will be use by three of them called :core.
The app it self can be found here movieapp

Project Structure

The core module is the place for providing the database and remote data, then the module will call the MovieUseCase for getting the access to them, but the module that can access the MovieUseCase is just only the :app module, because hilt can’t obtain it dependency to the dynamic feature app (:moviedetail and :favmovie)

The dependency of DFM is reversed, so the :app module cannot access any of DFM classes and the dagger tree cannot be constructed. but dont worry, hilt give us the solution.

To provide the dynamic feature app, we can obtain it by using the @EntryPoint annotation

@EntryPoint
@InstallIn(SingletonComponent::class)
interface SubModuleDependencies {
fun proiveMovieUseCase(): MovieUseCase
}

everything that we provide inside SubModuleDependencies interface will be obtained outside the scope of hilt, so the DFM can get the dependency of the MovieUseCase. Remember we have to write the SubModuleDependencies interface on the hilt scope.

And then we can create the component that need the SubmoduleDependencies as it dependencies

@Component(
dependencies = [SubModuleDependencies::class],
modules = [FavMovieViewModelModule::class]
)
@Singleton
interface FavMovieComponent {
fun inject(activity: FavMovieActivity)
@Component.Builder
interface Builder {
fun context(@BindsInstance context: Context): Builder
fun dependencies(component: SubModuleDependencies): Builder
fun build(): FavMovieComponent
}
}

Then we can provide the SubModuleDependencies using the EntryPointAccessors, after that we can inject the FavMovieComponent in the DFM Activity or Fragment

DaggerFavMovieComponent.builder()
.context(applicationContext)
.dependencies(
EntryPointAccessors.fromApplication(
applicationContext,
SubModuleDependencies::class.java
)
)
.build()
.inject(this)

And then build the project, see the project will run perfectly without error
Congratulations we have implemented Hilt in our DFM project! 🥳

For more detail about the implementation, you can see this DFM App movieapp, then checkout branch

  • master for Dagger 2
  • hilt_multi_module for Hilt

Thank you for visiting this article, hope it can be useful for us 😁

For more info you can check this link below

  1. https://developer.android.com/training/dependency-injection/dagger-multi-module
  2. https://medium.com/androiddevelopers/hilt-and-dagger-annotations-cheat-sheet-9adea070e495
  3. https://dagger.dev/hilt/benefits.html
  4. https://dagger.dev/hilt/components.html
  5. https://medium.com/androiddevelopers/dagger-code-generation-cheat-sheets-6b4fa2da4e7a