RepositoryModule

@Module
abstract class RepositoryModule

Hilt module binding repository interfaces to their implementations.

Why This Module Exists

This module follows the Dependency Inversion Principle by:

  1. Defining repository contracts as interfaces (in :data/repository)

  2. Implementing those contracts (in :data/repository/<Repository>Impl)

  3. 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): HomeRepository

3. 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

  1. Performance: Avoid creating duplicate repository instances

  2. Consistency: All ViewModels see the same data source

  3. Resource Management: Database and network clients are expensive to create

  4. 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 :data module

  • Ensure 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

See also

Constructors

Link copied to clipboard
constructor()