Using Dagger in multi-module apps

A project with multiple Gradle modules is known as a multi-module project. In a multi-module project that ships as a single APK with no feature modules, it's common to have an app module that can depend on most modules of your project and a base or core module that the rest of the modules usually depend on. The app module typically contains your Application class, whereas the base module contains all common classes shared across all modules in your project.

The app module is a good place to declare your application component (for example, ApplicationComponent in the image below) that can provide objects that other components might need as well as the singletons of your app. As an example, classes like OkHttpClient, JSON parsers, accessors for your database, or SharedPreferences objects that may be defined in the core module, will be provided by the ApplicationComponent defined in the app module.

In the app module, you could also have other components with shorter lifespans. An example could be a UserComponent with user-specific configuration (like a UserSession) after a log in.

In the different modules of your project, you can define at least one subcomponent that has logic specific to that module as seen in figure 1.

Figure 1. Example of a Dagger graph in a multi-module project

For example, in a login module, you could have a LoginComponent scoped with a custom @ModuleScope annotation that can provide objects common to that feature such as a LoginRepository. Inside that module, you can also have other components that depend on a LoginComponent with a different custom scope, for example @FeatureScope for a LoginActivityComponent or a TermsAndConditionsComponent where you can scope more feature-specific logic such as ViewModel objects.

For other modules such as Registration, you would have a similar setup.

A general rule for a multi-module project is that modules of the same level shouldn't depend on each other. If they do, consider whether that shared logic (the dependencies between them) should be part of the parent module. If so, refactor to move the classes to the parent module; if not, create a new module that extends the parent module and have both of the original modules extend the new module.

As a best practice, you would generally create a component in a module in the following cases:

  • You need to perform field injection, as with LoginActivityComponent.

  • You need to scope objects, as with LoginComponent.

If neither of these casses apply and you need to tell Dagger how to provide objects from that module, create and expose a Dagger module with @Provides or @Binds methods if construction injection is not possible for those classes.

Implementation with Dagger subcomponents

The Using Dagger in Android apps doc page covers how to create and use subcomponents. However, you cannot use the same code because feature modules don't know about the app module. As an example, if you think about a typical Login flow and the code we have in the previous page, it doesn't compile any more:

Kotlin

classLoginActivity:Activity(){
...
overridefunonCreate(savedInstanceState:Bundle?){
// Creation of the login graph using the application graph
loginComponent=(applicationContextasMyDaggerApplication)
.appComponent.loginComponent().create()
// Make Dagger instantiate @Inject fields in LoginActivity
loginComponent.inject(this)
...
}
}

Java

publicclass LoginActivityextendsActivity{
...
@Override
protectedvoidonCreate(BundlesavedInstanceState){
// Creation of the login graph using the application graph
loginComponent=((MyApplication)getApplicationContext())
.appComponent.loginComponent().create();
// Make Dagger instantiate @Inject fields in LoginActivity
loginComponent.inject(this);
...
}
}

The reason is that the login module doesn't know about MyApplication nor appComponent. To make it work, you need to define an interface in the feature module that provides a FeatureComponent that MyApplication needs to implement.

In the following example, you can define a LoginComponentProvider interface that provides a LoginComponent in the login module for the Login flow:

Kotlin

interfaceLoginComponentProvider{
funprovideLoginComponent():LoginComponent
}

Java

publicinterface LoginComponentProvider{
publicLoginComponentprovideLoginComponent();
}

Now, the LoginActivity will use that interface instead of the snippet of code defined above:

Kotlin

classLoginActivity:Activity(){
...
overridefunonCreate(savedInstanceState:Bundle?){
loginComponent=(applicationContextasLoginComponentProvider)
.provideLoginComponent()
loginComponent.inject(this)
...
}
}

Java

publicclass LoginActivityextendsActivity{
...
@Override
protectedvoidonCreate(BundlesavedInstanceState){
loginComponent=((LoginComponentProvider)getApplicationContext())
.provideLoginComponent();
loginComponent.inject(this);
...
}
}

Now, MyApplication needs to implement that interface and implement the required methods:

Kotlin

classMyApplication:Application(),LoginComponentProvider{
// Reference to the application graph that is used across the whole app
valappComponent=DaggerApplicationComponent.create()
overridefunprovideLoginComponent():LoginComponent{
returnappComponent.loginComponent().create()
}
}

Java

publicclass MyApplicationextendsApplicationimplementsLoginComponentProvider{
// Reference to the application graph that is used across the whole app
ApplicationComponentappComponent=DaggerApplicationComponent.create();
@Override
publicLoginComponentprovideLoginComponent(){
returnappComponent.loginComponent.create();
}
}

This is how you can use Dagger subcomponents in a multi-module project. With feature modules, the solution is different due to the way modules depend on each other.

Component dependencies with feature modules

With feature modules, the way modules usually depend on each other is inverted. Instead of the app module including feature modules, the feature modules depend on the app module. See figure 2 for a representation of how modules are structured.

Figure 2. Example of a Dagger graph in a project with feature modules

In Dagger, components need to know about their subcomponents. This information is included in a Dagger module added to the parent component (like the SubcomponentsModule module in Using Dagger in Android apps).

Unfortunately, with the reversed dependency between the app and the feature module, the subcomponent is not visible from the app module because it's not in the build path. As an example, a LoginComponent defined in a login feature module cannot be a subcomponent of the ApplicationComponent defined in the app module.

Dagger has a mechanism called component dependencies that you can use to solve this issue. Instead of the child component being a subcomponent of the parent component, the child component is dependent on the parent component. With that, there is no parent-child relationship; now components depend on others to get certain dependencies. Components need to expose types from the graph for dependent components to consume them.

For example: a feature module called login wants to build a LoginComponent that depends on the AppComponent available in the app Gradle module.

Below are definitions for the classes and the AppComponent that are part of the app Gradle module:

Kotlin

// UserRepository's dependencies
classUserLocalDataSource@Injectconstructor(){...}
classUserRemoteDataSource@Injectconstructor(){...}
// UserRepository is scoped to AppComponent
@Singleton
classUserRepository@Injectconstructor(
privatevallocalDataSource:UserLocalDataSource,
privatevalremoteDataSource:UserRemoteDataSource
){...}
@Singleton
@Component
interfaceAppComponent{...}

Java

// UserRepository's dependencies
publicclass UserLocalDataSource{
@Inject
publicUserLocalDataSource(){}
}
publicclass UserRemoteDataSource{
@Inject
publicUserRemoteDataSource(){}
}
// UserRepository is scoped to AppComponent
@Singleton
publicclass UserRepository{
privatefinalUserLocalDataSourceuserLocalDataSource;
privatefinalUserRemoteDataSourceuserRemoteDataSource;
@Inject
publicUserRepository(UserLocalDataSourceuserLocalDataSource,UserRemoteDataSourceuserRemoteDataSource){
this.userLocalDataSource=userLocalDataSource;
this.userRemoteDataSource=userRemoteDataSource;
}
}
@Singleton
@Component
publicinterface ApplicationComponent{...}

In your login gradle module that includes the app gradle module, you have a LoginActivity that needs a LoginViewModel instance to be injected:

Kotlin

// LoginViewModel depends on UserRepository that is scoped to AppComponent
classLoginViewModel@Injectconstructor(
privatevaluserRepository:UserRepository
){...}

Java

// LoginViewModel depends on UserRepository that is scoped to AppComponent
publicclass LoginViewModel{
privatefinalUserRepositoryuserRepository;
@Inject
publicLoginViewModel(UserRepositoryuserRepository){
this.userRepository=userRepository;
}
}

LoginViewModel has a dependency on UserRepository that is available and scoped to AppComponent. Let's create a LoginComponent that depends on AppComponent to inject LoginActivity:

Kotlin

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies=[AppComponent::class])
interfaceLoginComponent{
funinject(activity:LoginActivity)
}

Java

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies=AppComponent.class)
publicinterface LoginComponent{
voidinject(LoginActivityloginActivity);
}

LoginComponent specifies a dependency on AppComponent by adding it to the dependencies parameter of the component annotation. Because LoginActivity will be injected by Dagger, add the inject() method to the interface.

When creating a LoginComponent, an instance of AppComponent needs to be passed in. Use the component factory to do it:

Kotlin

@Component(dependencies=[AppComponent::class])
interfaceLoginComponent{
@Component.Factory
interfaceFactory{
// Takes an instance of AppComponent when creating
// an instance of LoginComponent
funcreate(appComponent:AppComponent):LoginComponent
}
funinject(activity:LoginActivity)
}

Java

@Component(dependencies=AppComponent.class)
publicinterface LoginComponent{
@Component.Factory
interface Factory{
// Takes an instance of AppComponent when creating
// an instance of LoginComponent
LoginComponentcreate(AppComponentappComponent);
}
voidinject(LoginActivityloginActivity);
}

Now, LoginActivity can create an instance of LoginComponent and call the inject() method.

Kotlin

classLoginActivity:Activity(){
// You want Dagger to provide an instance of LoginViewModel from the Login graph
@InjectlateinitvarloginViewModel:LoginViewModel
overridefunonCreate(savedInstanceState:Bundle?){
// Gets appComponent from MyApplication available in the base Gradle module
valappComponent=(applicationContextasMyApplication).appComponent
// Creates a new instance of LoginComponent
// Injects the component to populate the @Inject fields
DaggerLoginComponent.factory().create(appComponent).inject(this)
super.onCreate(savedInstanceState)
// Now you can access loginViewModel
}
}

Java

publicclass LoginActivityextendsActivity{
// You want Dagger to provide an instance of LoginViewModel from the Login graph
@Inject
LoginViewModelloginViewModel;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
// Gets appComponent from MyApplication available in the base Gradle module
AppComponentappComponent=((MyApplication)getApplicationContext()).appComponent;
// Creates a new instance of LoginComponent
// Injects the component to populate the @Inject fields
DaggerLoginComponent.factory().create(appComponent).inject(this);
// Now you can access loginViewModel
}
}

LoginViewModel depends on UserRepository; and for LoginComponent to be able to access it from AppComponent, AppComponent needs to expose it in its interface:

Kotlin

@Singleton
@Component
interfaceAppComponent{
funuserRepository():UserRepository
}

Java

@Singleton
@Component
publicinterface AppComponent{
UserRepositoryuserRepository();
}

The scoping rules with dependent components work in the same way as with subcomponents. Because LoginComponent uses an instance of AppComponent, they cannot use the same scope annotation.

If you wanted to scope LoginViewModel to LoginComponent, you would do it as you did previously using the custom @ActivityScope annotation.

Kotlin

@ActivityScope
@Component(dependencies=[AppComponent::class])
interfaceLoginComponent{...}
@ActivityScope
classLoginViewModel@Injectconstructor(
privatevaluserRepository:UserRepository
){...}

Java

@ActivityScope
@Component(dependencies=AppComponent.class)
publicinterface LoginComponent{...}
@ActivityScope
publicclass LoginViewModel{
privatefinalUserRepositoryuserRepository;
@Inject
publicLoginViewModel(UserRepositoryuserRepository){
this.userRepository=userRepository;
}
}

Best practices

  • The ApplicationComponent should always be in the app module.

  • Create Dagger components in modules if you need to perform field injection in that module or you need to scope objects for a specific flow of your application.

  • For Gradle modules that are meant to be utilities or helpers and don't need to build a graph (that's why you'd need a Dagger component), create and expose public Dagger modules with @Provides and @Binds methods of those classes that don't support constructor injection.

  • To use Dagger in an Android app with feature modules, use component dependencies to be able to access dependencies provided by the ApplicationComponent defined in the app module.

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年03月05日 UTC.