What you'll learn
As your app grows, wiring objects together by hand becomes painful and error-prone. Dependency injection automates it, and Hilt is Google's recommended DI framework for Android. It's the professional finish on the architecture you built in Phases 3 and 4 — and it makes testing dramatically easier.
By the end you'll be able to:
- Explain what dependency injection is and why it helps
- Set up Hilt in an app
- Provide dependencies with
@Inject,@Module, and@Provides - Inject repositories and ViewModels effortlessly
What dependency injection is
A dependency is any object a class needs to do its job — a ViewModel needs a repository, a repository needs a DAO and an API. Dependency injection means those objects are given to a class (usually through its constructor) rather than the class creating them itself. You've actually been doing this all along — passing a repository into a ViewModel is DI. The question is who builds and supplies them:
// Without DI: every screen manually builds the whole dependency chain. 😫
val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
val api = retrofit.create(NewsApi::class.java)
val repository = DefaultArticleRepository(db.articleDao(), api)
val viewModel = NewsViewModel(repository)Doing that by hand in every screen is repetitive and fragile. A DI framework builds the whole graph for you, ensures single instances where you want them, and lets you swap implementations for tests.
Key idea
Setting up Hilt
After adding the Gradle plugin and dependencies, two annotations bootstrap Hilt: mark your Application with @HiltAndroidApp, and each entry-point Activity with @AndroidEntryPoint:
// build.gradle: apply the Hilt plugin + kapt/ksp, add hilt-android + compiler.
@HiltAndroidApp // 1) annotate the Application
class MyApp : Application()
@AndroidEntryPoint // 2) annotate the Activity
class MainActivity : ComponentActivity() { /* … */ }@Inject & modules
Tell Hilt how to build a class by annotating its constructor with @Inject — now Hilt can create it and supply its dependencies. But Hilt can't construct everything itself: interfaces (which have no constructor) and types from libraries (Retrofit, Room). For those you write a module — @Provides functions that build them, and @Binds to map an interface to its implementation:
// Constructor injection: Hilt learns how to build these.
class DefaultArticleRepository @Inject constructor(
private val dao: ArticleDao,
private val api: NewsApi,
) : ArticleRepository
// A module tells Hilt how to provide things it can't construct itself
// (interfaces, or types from libraries like Retrofit/Room).
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Provides @Singleton
fun provideApi(): NewsApi =
Retrofit.Builder().baseUrl(BASE_URL) /* … */ .build().create(NewsApi::class.java)
@Provides @Singleton
fun provideDb(@ApplicationContext ctx: Context): AppDatabase =
Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()
@Provides fun provideDao(db: AppDatabase): ArticleDao = db.articleDao()
}
// Bind the interface to its implementation:
@Module @InstallIn(SingletonComponent::class)
abstract class RepoModule {
@Binds abstract fun bindRepo(impl: DefaultArticleRepository): ArticleRepository
}@Singleton means "create one and reuse it" — exactly right for a database or Retrofit instance. @InstallIn(SingletonComponent::class) says these live for the whole app.
Injecting ViewModels
The magic moment: annotate a ViewModel with @HiltViewModel and @Inject its constructor, and Hilt supplies the repository automatically. In Compose, get it with hiltViewModel() — no factory, no manual construction, no passing dependencies down through screens:
@HiltViewModel
class NewsViewModel @Inject constructor(
private val repository: ArticleRepository, // provided automatically
) : ViewModel() { /* … */ }
// In Compose — no factory, no manual wiring:
@Composable
fun NewsScreen(viewModel: NewsViewModel = hiltViewModel()) { /* … */ }Tip
hiltViewModel() → ViewModel → repository → DAO + API — and builds it all. You declare what each class needs; Hilt figures out how to provide it.Recap & quick check
Key takeaways
- Dependency injection means giving a class its dependencies instead of it creating them.
- Hilt automates building the dependency graph and enables swapping implementations for tests.
- Bootstrap with @HiltAndroidApp on the Application and @AndroidEntryPoint on Activities.
- Use @Inject constructors for your own classes; @Module + @Provides/@Binds for interfaces and library types.
- @HiltViewModel + hiltViewModel() inject ViewModels (and their dependencies) with zero manual wiring.
Quick check
1. What is dependency injection?
2. The biggest practical benefit of DI is…
3. Which annotations bootstrap Hilt?
4. How does Hilt provide an interface like ArticleRepository?
5. How do you obtain a Hilt-injected ViewModel in Compose?
Your app is wired together cleanly. Now let's prove it works with automated tests.