RepositoryModule
Hilt module binding repository interfaces to their implementations.
Why This Module Exists
This module follows the Dependency Inversion Principle by:
Defining repository contracts as interfaces (in
:data/repository)Implementing those contracts (in
:data/repository/<Repository>Impl)Binding implementations to interfaces via Hilt
Benefits of Interface + Implementation Pattern
1. Testability
// In tests, inject a fake repository
class FakeHomeRepository : HomeRepository {
override suspend fun getJetpacks() = flowOf(testData)
}2. Flexibility
Can swap implementations without changing consuming code:
// Switch from Firebase to REST API
@Binds fun bindHome(impl: HomeRepositoryRestImpl): HomeRepository3. Separation of Concerns
Interface defines WHAT operations are available
Implementation defines HOW those operations work
ViewModels depend on WHAT, not HOW
Why @Binds Instead of @Provides
This module uses @Binds instead of @Provides for performance:
@Provides (NOT USED)
@Provides
fun provideRepo(impl: HomeRepositoryImpl): HomeRepository = impl
// Generates: return impl (runtime object creation)@Binds (USED)
@Binds
abstract fun bindRepo(impl: HomeRepositoryImpl): HomeRepository
// Generates: cast at compile-time (no runtime overhead)Result: @Binds is more efficient because it's just a type cast, not object creation.
Why @Singleton
All repositories are annotated with @Singleton, which means:
What It Does
Repository instance is created once and reused across the app
Same instance is injected into all ViewModels
In-memory state is shared (if any)
Why This Is Correct
Performance: Avoid creating duplicate repository instances
Consistency: All ViewModels see the same data source
Resource Management: Database and network clients are expensive to create
Cache Sharing: Any in-memory caching is shared across ViewModels
Example
// Both ViewModels get THE SAME repository instance
class HomeViewModel @Inject constructor(
private val repo: HomeRepository // Instance #1
)
class ItemViewModel @Inject constructor(
private val repo: HomeRepository // Same instance #1
)Why internal Visibility
Functions are marked internal to:
Prevent external modules from depending on implementation details
Keep implementation details within the
:datamoduleEnsure only interfaces are exposed to feature modules
Architecture Decision
This pattern was chosen because:
Clean Architecture: Separates contracts from implementation
Testability: Easy to mock repositories in unit tests
Flexibility: Can swap data sources (Firebase → REST → Mock) without changing ViewModels
Compile Safety: ViewModels compile against interfaces, not implementations