Why Dependency Injection?
Dependency Injection provides several benefits:Testability
Easy to swap real implementations with mocks for testing
Decoupling
Classes don’t create their dependencies, making them more flexible
Reusability
Dependencies can be shared across multiple classes
Maintainability
Changing implementations doesn’t require modifying dependent classes
Hilt Setup
Application Class
The app is annotated with@HiltAndroidApp to enable dependency injection:
com/bsvillarraga/spaceflightnews/MyApp.kt
This annotation triggers Hilt’s code generation and initializes the dependency graph when the app starts.
Modules
Hilt uses modules to define how to provide dependencies. The app has two main modules:- NetworkModule - Provides network-related dependencies
- RoomModule - Provides database-related dependencies
NetworkModule
This module provides all network and repository dependencies:com/bsvillarraga/spaceflightnews/di/NetworkModule.kt
Key Annotations
@Module
@Module
Marks a class as a Hilt module. Modules tell Hilt how to provide instances of certain types.
@InstallIn(SingletonComponent::class)
@InstallIn(SingletonComponent::class)
Specifies which Hilt component this module should be installed in.
SingletonComponent means these dependencies live as long as the application.Other options include:ActivityComponent- Scoped to an Activity’s lifecycleFragmentComponent- Scoped to a Fragment’s lifecycleViewModelComponent- Scoped to a ViewModel’s lifecycle
@Provides
@Provides
Marks a function that provides a dependency. The return type tells Hilt what this function provides.
@Singleton
@Singleton
Ensures only one instance of this dependency exists throughout the app’s lifetime.
@ApplicationContext
@ApplicationContext
Qualifier annotation that tells Hilt to inject the Application context, not an Activity context.
Dependency Chain in NetworkModule
Notice how dependencies reference each other:Hilt automatically resolves the dependency graph. When you request
ArticleRepository, Hilt knows it needs ArticlesApiClient, ApiHelper, and PaginationManager, and provides them automatically.Retrofit Configuration
The Retrofit provider includes a custom interceptor:com/bsvillarraga/spaceflightnews/di/NetworkModule.kt:33-48
Repository Binding
The repository provider binds the interface to its implementation:com/bsvillarraga/spaceflightnews/di/NetworkModule.kt:84-91
The return type is
ArticleRepository (interface), but the implementation is ArticleRepositoryImpl. This allows the domain and presentation layers to depend on the interface only.RoomModule
This module provides database dependencies:com/bsvillarraga/spaceflightnews/di/RoomModule.kt
Database Dependency
Database Dependency
Hilt provides the Room database instance as a singleton, ensuring only one database connection exists.
DAO Dependency
DAO Dependency
The DAO provider depends on the database. Hilt automatically provides the database instance.
Constructor Injection
Classes that Hilt can create automatically use@Inject on their constructor:
Repository Implementation
com/bsvillarraga/spaceflightnews/data/repository/ArticleRepositoryImpl.kt:29-33
The
@Inject annotation tells Hilt this class can be constructed by providing these three dependencies. No need for a @Provides function in a module.Use Cases
com/bsvillarraga/spaceflightnews/domain/usecase/GetArticlesUseCase.kt:15-17
ArticleRepository interface.
ViewModels
ViewModels use a special annotation:com/bsvillarraga/spaceflightnews/presentation/ui/articles/viewmodel/ArticlesViewModel.kt:20-24
Injection Points
Fragments
Fragments use@AndroidEntryPoint to enable field injection:
com/bsvillarraga/spaceflightnews/presentation/ui/articles/ArticlesFragment.kt:41-49
The
by viewModels() delegate automatically obtains the ViewModel from Hilt with all its dependencies injected.Activities
Activities also use@AndroidEntryPoint:
Dependency Graph Visualization
Here’s how the complete dependency graph flows:Benefits in Practice
No Manual Wiring
No Manual Wiring
Without DI, you’d need code like:With Hilt, it’s just:
Easy Testing
Easy Testing
In tests, you can provide mock implementations:
Lifecycle Management
Lifecycle Management
Hilt automatically handles lifecycle:
- Singletons live for the entire app
- Activity-scoped dependencies die with the Activity
- ViewModel-scoped dependencies survive configuration changes
Compile-Time Safety
Compile-Time Safety
Hilt generates code at compile time. If dependencies are missing or circular, you get a compile error, not a runtime crash.
Common Patterns
Singleton Dependencies
Use@Singleton for dependencies that should be shared:
Good for Singletons
- Network clients (Retrofit, OkHttpClient)
- Database instances (Room)
- Repositories
- SharedPreferences
- Analytics/Logging services
Unscoped Dependencies
Omit@Singleton for dependencies that should be created fresh:
Don't Use Singletons For
- Stateful objects that shouldn’t be shared
- Objects with short lifecycles
- Test doubles in unit tests
Interface Binding
Always return interfaces when possible:This allows you to swap implementations without changing dependent classes.
Debugging Tips
Check Return Types
@Provides functions should return the type you want to inject (usually interfaces)Related Resources
Architecture Overview
See how DI fits into the overall architecture
Clean Architecture
Understand the layers that DI connects