Dependency Injection¶
The Cryptographer application uses Hilt (built on Dagger) for dependency injection. Hilt provides a standard way to incorporate Dagger dependency injection into an Android application.
Overview¶
Hilt simplifies dependency injection by: - Reducing boilerplate code - Providing Android-specific components - Automatically managing component lifecycles - Integrating with Android classes
Setup¶
Application Class¶
The application class is annotated with @HiltAndroidApp:
@HiltAndroidApp
class CryptographerApplication : Application() {
// Application initialization
}
Registered in AndroidManifest.xml:
DI Module¶
Dependency injection is configured in setup/ioc/AppModule.kt:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideAesEncryptionService(): AesEncryptionService {
return AesEncryptionService()
}
@Provides
@Singleton
fun provideChaCha20EncryptionService(): ChaCha20EncryptionService {
return ChaCha20EncryptionService()
}
// More providers...
}
Component Hierarchy¶
Hilt provides Android-specific components:
Application
└── SingletonComponent (Application scope)
└── ActivityComponent (Activity scope)
└── ViewModelComponent (ViewModel scope)
Injection Points¶
ViewModels¶
ViewModels are annotated with @HiltViewModel:
@HiltViewModel
class KeyGenerationViewModel @Inject constructor(
private val generateKeyCommand: AesGenerateAndSaveKeyCommandHandler,
private val readAllKeysQuery: ReadAllKeysQueryHandler
) : ViewModel() {
// ViewModel logic
}
Activities¶
Activities are annotated with @AndroidEntryPoint:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
// Activity code
}
Constructor Injection¶
Dependencies are injected via constructor:
class AesGenerateAndSaveKeyCommandHandler @Inject constructor(
private val encryptionService: AesEncryptionService,
private val keyStorage: KeyCommandGateway
) {
// Handler logic
}
Scopes¶
Singleton Scope¶
Services and use cases are provided as singletons:
@Provides
@Singleton
fun provideAesEncryptionService(): AesEncryptionService {
return AesEncryptionService()
}
ViewModel Scope¶
ViewModels are scoped to their lifecycle:
@HiltViewModel
class KeyGenerationViewModel @Inject constructor(
// Dependencies injected here
) : ViewModel()
Provided Dependencies¶
Domain Services¶
@Provides
@Singleton
fun provideAesEncryptionService(): AesEncryptionService
@Provides
@Singleton
fun provideChaCha20EncryptionService(): ChaCha20EncryptionService
@Provides
@Singleton
fun provideTextService(): TextService
Command Handlers¶
// Automatically injected via constructor
class AesGenerateAndSaveKeyCommandHandler @Inject constructor(...)
class ChaCha20GenerateAndSaveKeyCommandHandler @Inject constructor(...)
class DeleteKeyCommandHandler @Inject constructor(...)
// ... more handlers
Query Handlers¶
// Automatically injected via constructor
class ReadAllKeysQueryHandler @Inject constructor(...)
class ReadKeyByIdQueryHandler @Inject constructor(...)
// ... more handlers
Infrastructure Adapters¶
// Automatically injected via constructor
class KeyCommandGatewayAdapter @Inject constructor(
private val context: Context
) : KeyCommandGateway
class KeyQueryGatewayAdapter @Inject constructor(
private val context: Context
) : KeyQueryGateway
Context Injection¶
Android Context is provided by Hilt:
class KeyCommandGatewayAdapter @Inject constructor(
@ApplicationContext private val context: Context
) : KeyCommandGateway {
// Use context for SharedPreferences, etc.
}
Testing¶
Hilt provides test-specific components for testing:
@HiltAndroidTest
class KeyGenerationViewModelTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Before
fun init() {
hiltRule.inject()
}
// Test code
}
Benefits¶
- Reduced Boilerplate: Less manual dependency management
- Type Safety: Compile-time dependency checking
- Testability: Easy to provide test doubles
- Lifecycle Awareness: Automatic component lifecycle management
- Scalability: Easy to add new dependencies
Best Practices¶
- Use Constructor Injection: Prefer constructor injection over field injection
- Scope Appropriately: Use singleton for stateless services
- Avoid Circular Dependencies: Design dependencies carefully
- Provide Interfaces: Inject interfaces, not concrete classes
- Test with Test Doubles: Use Hilt’s test components
Learn More¶
- Clean Architecture - Overall architecture
- Application Layer - How DI is used in application layer
- Hilt Documentation - Official Hilt documentation