Seamlessly Add New Features with Dagger 2 in Your Multi-Module Android App
Published on:
A practical guide to integrating a new feature module with its own Dagger 2 setup into an existing multi-module Android project that uses shared core dependencies.

A practical guide to integrating a new feature module with its own Dagger 2 setup into an existing multi-module Android project that uses shared core dependencies.
Introduction
So, your Android app is growing, and you’re embracing a multi-module architecture. You’ve already set up Dagger 2 to manage dependencies, perhaps with a :core
or :app
module sharing common dependencies with existing features. Now, it’s time to add a brand-new feature – let’s say a “User Feedback” section – as its own module. How do you wire this new feature into your Dagger graph cleanly and efficiently?
This guide provides a step-by-step approach to adding a new feature module (:feature_feedback
in our examples) to your Dagger 2-enabled multi-module project, ensuring it can access shared dependencies while managing its own.
Prerequisites:
- An existing Android multi-module project.
- Dagger 2 already configured, with a mechanism for sharing core dependencies. This typically involves:
- A common interface (e.g.,
CoreDependencies
) defining shared bindings. - A provider for this interface (e.g., your
Application
class implementingCoreDependenciesProvider
). - The main application component (
AppComponent
or similar) fulfilling theCoreDependencies
contract. - You’re using Kotlin and likely KSP (or kapt) for Dagger’s annotation processing.
Step 1: Create Your New Feature Module
First, create the new Android Library module for your feature (e.g., :feature_feedback
).
Step 2: Configure Gradle Dependencies for the New Feature Module
In the build.gradle.kts
(or build.gradle
) file of your new :feature_feedback
module, you’ll need to add dependencies for Dagger and your common/core dependencies module.
// :feature_feedback/build.gradle.kts
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation(project(":core")) // Or your common module providing CoreDependencies
// Dagger
implementation("com.google.dagger:dagger:2.XX")
// configure KSP for Dagger
ksp("com.google.dagger:dagger-compiler:$daggerVersion")
// Other AndroidX, UI, and feature-specific libraries
implementation("androidx.core:core-ktx:...")
implementation("androidx.appcompat:appcompat:...")
}
Key Points:
implementation(project(":core"))
: This allows:feature_feedback
to access theCoreDependencies
interface.- Ensure Dagger versions match your existing project setup.
Step 3: Define the Feature’s Dagger Component
Each feature module should have its own Dagger component to manage its internal dependency graph and to act as a bridge to shared dependencies.
- Create a Custom Scope (Recommended): This helps manage the lifecycle of dependencies specific to this feature.
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FeedbackFeatureScope
- Create the Feature Component Interface:
This component will declare
CoreDependencies
as a component dependency.
import com.example.feature_feedback.ui.ExampleActivity
import dagger.Component
@FeedbackFeatureScope
@Component(
dependencies = [CoreDependencies::class],
modules = [ExampleFeatureModule::class]
)
interface ExampleFeatureComponent {
// Define inject methods for your feature's Activities, Fragments, ViewModels, etc.
fun inject(activity: FeedbackActivity)
// fun inject(fragment: FeedbackFragment)
// Dagger requires a factory or builder to create the component
@Component.Factory
interface Factory {
fun create(
coreDependencies: CoreDependencies
// You could also add @BindsInstance here for objects needed at component creation time
// that aren't part of CoreDependencies or FeedbackFeatureModule.
): FeedbackFeatureComponent
}
}
Explanation:
@FeedbackFeatureScope
: Annotates the component with our custom scope.dependencies = [CoreDependencies::class]
: This is crucial. It tells Dagger thatFeedbackFeatureComponent
requires an instance ofCoreDependencies
to be constructed. Dagger will then make all provisions fromCoreDependencies
available withinFeedbackFeatureComponent
.modules = [FeedbackFeatureModule::class]
: If your feature has its own specific dependencies to provide (like ViewModels, Repositories specific to feedback), you’ll list their Dagger modules here.@Component.Factory
: The standard way to create an instance of this component, allowing you to pass in the requiredCoreDependencies
.
Step 4: (Optional) Create Feature-Specific Dagger Modules
If your new feature has its own set of dependencies (e.g., FeedbackViewModel
, FeedbackRepository
), create Dagger modules within the feature module to provide them.
// In :feature_feedback/di/FeedbackFeatureModule.kt package com.example.feature_feedback.diimport com.example.core.domain.UserRepository // Example: Consuming a dependency from CoreDependencies import com.example.feature_feedback.data.FeedbackRepositoryImpl import com.example.feature_feedback.domain.FeedbackRepository import com.example.feature_feedback.presentation.FeedbackViewModel import dagger.Module import dagger.Provides@Module class FeedbackFeatureModule {@Provides
@FeedbackFeatureScope // Scope ViewModel to the feature's lifecycle
fun provideFeedbackViewModel(
feedbackRepository: FeedbackRepository,
userRepository: UserRepository // Provided by CoreDependencies
): FeedbackViewModel {
return FeedbackViewModel(feedbackRepository, userRepository)
}
@Provides
@FeedbackFeatureScope // Scope repository if it has state or is expensive to create
fun provideFeedbackRepository(/* ...dependencies for repository... */): FeedbackRepository {
return FeedbackRepositoryImpl(/* ... */)
}
// Add other providers specific to the feedback feature
Note: Dependencies like
UserRepository
are made available becauseFeedbackFeatureComponent
has >CoreDependencies
as a dependency.
Step 5: Instantiate and Inject in Your Feature’s Entry Point
Now, you need to create an instance of your FeedbackFeatureComponent
and use it to inject dependencies into your feature’s entry points (like Activities or Fragments).
// In :feature_feedback/ui/FeedbackActivity.kt package com.example.feature_feedback.uiimport android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.example.core.di.CoreDependenciesProvider // Your provider interface from :core import com.example.feature_feedback.di.DaggerFeedbackFeatureComponent // Generated class import javax.inject.Injectclass FeedbackActivity : AppCompatActivity() {@Inject
lateinit var viewModel: FeedbackViewModel // Dependencies will be injected here
// You might hold a reference to the component if sub-components or other parts need it,
// though often it's only needed during onCreate for initial injection.
// lateinit var feedbackComponent: FeedbackFeatureComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(...)
// 1. Get CoreDependencies from the Application context
val coreDependencies = (applicationContext as CoreDependenciesProvider)
.provideCoreDependencies()
// 2. Create the FeedbackFeatureComponent instance
val feedbackComponent = DaggerFeedbackFeatureComponent.factory()
.create(coreDependencies = coreDependencies)
// 3. Inject the Activity (or Fragment)
feedbackComponent.inject(this)
// Now 'viewModel' and other @Inject annotated fields are populated.
// You can start using them.
// e.g., setupObservers(viewModel)
}
Key Steps in onCreate
:
- Retrieve
CoreDependencies
: Cast yourapplicationContext
to yourCoreDependenciesProvider
(which yourApplication
class implements) and callprovideCoreDependencies()
. - Create Feature Component: Use the Dagger-generated
DaggerFeedbackFeatureComponent.factory()
to create an instance, passing in thecoreDependencies
. - Inject: Call the
inject()
method on your newly created component instance, passingthis
(the Activity/Fragment).
Step 6: Handling Potential “Could Not Be Resolved” Errors
A common pitfall when adding new features that use types from other modules (especially those exposed via CoreDependencies
) is the Dagger/KSP error:
[ksp] ComponentProcessingStep was unable to process '...FeedbackFeatureComponent' because 'com.example.domain.SomeTypeFromCore' could not be resolved.
-
Cause: The feature module’s compilation classpath doesn’t include the actual class definition for a type it’s trying to use (e.g.,
SomeTypeFromCore
which might be returned by a method inCoreDependencies
). -
Solution: Ensure your Gradle module dependencies are correctly configured using
api
where necessary.- If
CoreDependencies
(in:core
) exposesSomeTypeFromCore
(defined in:domain
), then the:core
module must depend on:domain
usingapi project(":domain")
. - This way, when
:feature_feedback
depends on:core
withimplementation project(":core")
, it transitively gets visibility to the types from:domain
.
Review the dependencies of the module defining
CoreDependencies
to ensure it usesapi
for modules whose types it exposes in its public interface. - If
Step 7: Build and Test
After these configurations:
- Rebuild your project. Dagger will generate the necessary
DaggerFeedbackFeatureComponent
class. - Run your app and navigate to your new feature to ensure dependencies are injected correctly and the feature functions as expected.
Conclusion
Adding a new feature module with Dagger 2 into an existing multi-module setup involves a few well-defined steps. By creating a dedicated feature component that depends on your shared CoreDependencies
, managing feature-specific dependencies in local Dagger modules, and correctly configuring Gradle, you can maintain a clean, scalable, and decoupled architecture.
This structured approach not only makes adding new features more straightforward but also reinforces the benefits of modularity and clear dependency management in large Android applications. Happy coding!
External references: