ViewModels, Testing & When Hilt Is Worth It
Scopes: Controlling Instance Lifetime
@Singleton means one instance for the whole app's lifetime; @ViewModelScoped ties an instance's lifetime to a specific ViewModel; unscoped bindings get a fresh instance every time they're requested. Choosing the right scope means matching a dependency's lifetime to the Android component it naturally belongs to.
@HiltViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel()@HiltViewModel integrates with Jetpack's ViewModelProvider machinery, letting a ViewModel's dependencies be injected via its constructor automatically — removing the need to hand-write a custom ViewModelProvider.Factory, a common pain point in pre-Hilt Dagger setups.
Testability & @HiltAndroidTest
Because a class receives dependencies via injection rather than constructing concrete implementations itself, tests can substitute a fake/mock for an interface without touching the class under test. @HiltAndroidTest extends this into instrumented tests, letting test-specific modules swap in fakes for particular dependencies while keeping the rest of the DI setup intact.
Compile-Time Safety, Not Runtime Reflection
An annotation processor scans @Inject/@Module/@Provides annotations at compile time and generates the actual dependency-graph code, compiled normally alongside the app. This catches misconfigured dependencies as build errors rather than runtime crashes, and avoids reflection overhead — both meaningful on resource-constrained mobile devices.
Is It Worth the Setup?
For an app with only a handful of simple dependencies, Hilt's setup and annotation-processing build-time cost might not clearly outweigh simpler manual dependency passing — but that calculation shifts quickly as the dependency graph grows and manual wiring becomes a real maintenance burden.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free