Sparround

Dependency Injection: Hilt, Dagger, Koin

Dependency Injection means an object does not create its own dependencies — they are supplied from outside. The ViewModel does not new a Repository — it receives one in its constructor.

Why it matters:

  • Testing — a fake repository can replace the real network
  • A single wiring point — objects like Retrofit/Room are configured in one place
  • Loose coupling — depend on interfaces, swap implementations

The tools:

  • Dagger 2 — compile-time DI: code generation, fast at runtime, steep learning curve.
  • Hilt — Dagger's standardized Android face: predefined components (SingletonComponent, ViewModelComponent), @HiltViewModel, @AndroidEntryPoint. Google's recommendation.
  • Koin — service-locator style, runtime resolution: a simple DSL, no codegen, but errors surface at runtime and large graphs resolve slower.
kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides @Singleton
    fun provideApi(retrofit: Retrofit): AccountApi =
        retrofit.create(AccountApi::class.java)
}

class AccountRepositoryImpl @Inject constructor(
    private val api: AccountApi,
    private val dao: AccountDao
) : AccountRepository { ... }

@HiltViewModel
class AccountViewModel @Inject constructor(
    private val repo: AccountRepository
) : ViewModel() { ... }

@AndroidEntryPoint
class AccountsFragment : Fragment() {
    private val viewModel: AccountViewModel by viewModels()
}

A typical Hilt graph: module → repository → ViewModel

Scope questions always come: @Singleton — one instance app-wide (Retrofit, Room); @ViewModelScoped — for the ViewModel's lifetime; an unscoped binding yields a new object per injection. Wrong scoping is a real bug: make the user session @Singleton carelessly and stale data can survive logout — in a banking app that is a serious security defect.

🛠 Practice task

Build a small Hilt graph: @Module → a Repository interface + implementation → @HiltViewModel.

  • Delete one binding on purpose and read the compile error — feel Dagger/Hilt's compile-time advantage.
  • Log the difference between @Singleton and an unscoped binding (print object hashes).
  • Write a FakeRepository and get one unit test passing against the ViewModel.

Done when: you can construct the ViewModel in a test without starting any Hilt component.

📚 Sources and documentation