Youky Design

menu

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.

Seamlessly Add New Features with Dagger 2 in Your Multi-Module Android Appthumbnail

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 implementing CoreDependenciesProvider).
  • The main application component (AppComponent or similar) fulfilling the CoreDependencies 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 the CoreDependencies 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.

  1. Create a Custom Scope (Recommended): This helps manage the lifecycle of dependencies specific to this feature.
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FeedbackFeatureScope
  1. 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 that FeedbackFeatureComponent requires an instance of CoreDependencies to be constructed. Dagger will then make all provisions from CoreDependencies available within FeedbackFeatureComponent.
  • 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 required CoreDependencies.

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 because FeedbackFeatureComponent 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:

  1. Retrieve CoreDependencies: Cast your applicationContext to your CoreDependenciesProvider (which your Application class implements) and call provideCoreDependencies().
  2. Create Feature Component: Use the Dagger-generated DaggerFeedbackFeatureComponent.factory() to create an instance, passing in the coreDependencies.
  3. Inject: Call the inject() method on your newly created component instance, passing this (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 in CoreDependencies).

  • Solution: Ensure your Gradle module dependencies are correctly configured using api where necessary.

    • If CoreDependencies (in :core) exposes SomeTypeFromCore (defined in :domain), then the :core module must depend on :domain using api project(":domain").
    • This way, when :feature_feedback depends on :core with implementation project(":core"), it transitively gets visibility to the types from :domain.

    Review the dependencies of the module defining CoreDependencies to ensure it uses api for modules whose types it exposes in its public interface.

Step 7: Build and Test

After these configurations:

  1. Rebuild your project. Dagger will generate the necessary DaggerFeedbackFeatureComponent class.
  2. 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: