💉Dependency Injection with Dagger Hilt

Dependency Injection (DI) is a design pattern that helps make Android apps more maintainable, modular, and testable. It reduces tight coupling between classes by delegating the responsibility of providing dependencies to a centralized system.

🔧 Adding Hilt to Your Project

1. Project-Level build.gradle.kts

Add the Hilt plugin declaration:

plugins {
    id("com.google.dagger.hilt.android") version "2.56.1" apply false //replace with current version
}

2. App-Level build.gradle.kts

Enable Hilt and KSP in the app module:

plugins {
    id("com.google.devtools.ksp")
    id("com.google.dagger.hilt.android")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.56.1") //replace with current version
    ksp("com.google.dagger:hilt-android-compiler:2.56.1") //replace with current version
}

🚀 Initialize Hilt in the Application Class

Create a class that extends Application and annotate it with @HiltAndroidApp. This class serves as the entry point for setting up Hilt’s dependency container.

@HiltAndroidApp
class BaseApp : Application()

Also, register this class in your AndroidManifest.xml:

<application
    android:name=".BaseApp"
    ... >
</application>

📥 Injecting Dependencies in Activities

To enable injection in an Activity, annotate it with @AndroidEntryPoint.

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    // Dependencies will be injected here
}

🧃 Example: Injecting a Custom Class

Suppose you have a simple class like this:

class JuiceMaker(private val flavor: String) {
    fun makeJuice(): String = "Here's your $flavor juice! 🧃"
}

To inject this with Hilt, create a module that provides it:

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

    @Provides
    @Singleton
    fun provideFlavor(): String = "mango"

    @Provides
    @Singleton
    fun provideJuiceMaker(flavor: String): JuiceMaker {
        return JuiceMaker(flavor)
    }

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .build()
    }
}

SingletonComponent ensures these dependencies live throughout the app's lifecycle.


🧩 Using Injected Dependencies

Now you can inject and use them directly in MainActivity:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    @Inject lateinit var juiceMaker: JuiceMaker
    @Inject lateinit var retrofit: Retrofit
    @Inject lateinit var flavor: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Column {
                Text(juiceMaker.makeJuice())
                Text(retrofit.baseUrl())
                Text("Flavor: $flavor")
            }
        }
    }
}

⚖️ Without DI vs With DI (Hilt)

Feature❌ Without DI✅ With Hilt (DI)
Object CreationManualAutomatic via @Inject and @Provides
ReusabilityDifficultEasily reusable across classes
TestabilityHard to mockCan be replaced with mocks in tests
ScalabilityBecomes tightly coupledModular and clean architecture
Lifecycle AwarenessManual lifecycle managementLifecycle-aware scoping (@Singleton, etc.)
FlexibilityConstructor changes require refactorHilt handles constructor injection updates

✅ Conclusion

Using Hilt for Dependency Injection helps simplify app architecture by:

  • Automating object creation and management

  • Promoting loose coupling and separation of concerns

  • Enhancing testability and maintainability

0
Subscribe to my newsletter

Read articles from Sagnik Mukherjee directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sagnik Mukherjee
Sagnik Mukherjee

Native Android Developer and content creator