Dagger HILT With Kotlin And Jetpack Compose
Look, I'll be honest with you — when I first heard about dependency injection in Android, I thought "great, another thing to make my life more complicated." Android development already feels overwhelming sometimes, and DI seemed like just another hurdle. But after working with Dagger HILT for a while, I realized it actually makes things simpler. Let me walk you through it.
What Even Is a Dependency?
Before we dive into the fancy stuff, let's clear this up. A dependency is just... the stuff your function needs to work. That's it. Look at this:
@Composable
fun MainScreen(viewModel: MyViewModel) {
// your UI code
}
See that viewModel? That's a dependency. MainScreen needs it to function.
Simple as that.
Why Can't I Just Pass Everything Manually?
You absolutely can! And for small projects, that's totally fine. But imagine you've got 50 functions, and they all need the same database instance or API client. Now imagine you decide to switch from Retrofit to Ktor. Have fun updating all 50 functions!
This is where dependency injection saves your sanity. You define how to create your objects once, and HILT handles the rest. Need that database in 20 different places? No problem. Want the same instance everywhere? Easy.
What's Dagger HILT Anyway?
HILT is basically your personal assistant for managing dependencies. It's a library that automatically creates and provides the objects you need, when you need them. Want a single copy of something shared across your entire app? HILT's got you. Need something that only lives as long as an Activity? HILT can do that too.
Let's Get Our Hands Dirty
Step 1: Adding Dependencies
First things first, we need to add HILT to our project. I'm using KSP here because it's faster than KAPT, but you can use either.
In your app-level build.gradle:
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
ksp("com.google.dagger:hilt-android-compiler:2.48")
ksp("androidx.hilt:hilt-compiler:1.2.0")
}
plugins {
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
In your project-level build.gradle:
plugins {
id("com.google.dagger.hilt.android") version "2.48" apply false
id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}
Step 2: Creating Modules
Modules are where you tell HILT how to create your objects. Think of them as recipe books. Create a package called "di" (short for dependency injection) and add an object called AppModule:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
// Your dependency recipes go here
}
@Module tells HILT "hey, this is where I define my dependencies."
@InstallIn defines the scope — how long these objects should live.
Understanding Scopes
- SingletonComponent::class — One instance for the entire app lifetime
- ActivityComponent::class — Lives as long as the Activity does
Step 3: Providing Dependencies
Now let's write some actual code. Say you need a Room database:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(
@ApplicationContext context: Context
): TaskDatabase {
return Room.databaseBuilder(
context,
TaskDatabase::class.java,
"task_database"
).build()
}
}
That @ApplicationContext is super handy — HILT automatically gives you
the app context without you having to pass it around manually.
Here's another example with Retrofit:
@Provides
@Singleton
fun provideApi(): MyAPI {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MyAPI::class.java)
}
Step 4: Injecting Into ViewModels
ViewModels work a bit differently. You annotate them with @HiltViewModel
and use @Inject constructor():
@HiltViewModel
class TaskViewModel @Inject constructor(
private val taskRepository: TaskRepository,
private val context: Context
) : ViewModel() {
// Your ViewModel logic
}
Whatever you put in that constructor, you need to provide in your AppModule. HILT will automatically wire everything up.
Step 5: Setting Up Your Application Class
Create a new file (I usually call it MyApp.kt):
@HiltAndroidApp
class MyApp : Application() {
// That's it! HILT does the rest
}
Then add it to your AndroidManifest.xml:
<application
android:name=".MyApp"
android:allowBackup="true"
...>
Step 6: Annotate Your MainActivity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Your code
}
}
Step 7: Using Your Dependencies
If you need to inject something directly into your Activity (though honestly, you'll mostly use ViewModels), you can do this:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var taskViewModel: TaskViewModel
@Inject
lateinit var categoryViewModel: CategoryViewModel
}
My Honest Take
I know this looks like a lot at first. When I started, I was like "why can't I just create objects the normal way?" But once you work on a real project with multiple screens, databases, API calls, and all that jazz, you'll appreciate HILT.
The best way to learn is to actually use it. Start small — maybe just inject a database or a simple repository. Once you see how it works, you'll wonder how you ever lived without it.
Trust me, future you will thank present you for learning this now.
Blog List