Runtime arguments with Hilt

Architecture and dependency injection Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Pick the fix

A detail ViewModel has fun init(id: String) called from the screen, and it reloads on every rotation. Best fix?

Options
Hint 1 of 1

Where do navigation arguments already live?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Read the id from SavedStateHandle in the ViewModel

Correct. The argument arrives with the ViewModel and is restored after process death.

Why not the others?
  • B Guard init with a Boolean flag. Works for rotation, but not after process death, and it's easy to get wrong.
  • C Call init in LaunchedEffect(Unit). Still runs again on rotation.
  • D Make the ViewModel a singleton. Then all detail screens share one.

30-second answer

For ViewModels, read navigation arguments from SavedStateHandle, which Hilt injects, so the value also survives process death. For other classes that need runtime values plus injected ones, use assisted injection: @AssistedInject constructor(@Assisted id: String, repo: Repo) with an @AssistedFactory interface. Hilt ViewModels also support assisted factories through hiltViewModel(creationCallback = ...).

Practise saying it · 30 s. No recording, just a timer

Explain passing runtime values with Hilt.

Say your answer out loud, then check which of these points you covered:

  • SavedStateHandle for screen args
  • Assisted injection for others
  • Don't construct ViewModels yourself
  • IDs, not objects

Deep dive

SavedStateHandle is the simplest option for screen arguments.

An assisted factory is an interface with one create(id) function that Hilt implements.

Don't create a ViewModel with its constructor to pass arguments; you lose the ViewModelStore.

Keep runtime arguments small: IDs, not objects.

Assisted injection Compile-checked · Kotlin 2.4.21
class Uploader @AssistedInject constructor(
    @Assisted private val fileName: String,
    private val repo: UserRepository,
) {
    fun describe() = "Uploading $fileName"
}

@AssistedFactory
interface UploaderFactory {
    fun create(fileName: String): Uploader
}

Common mistakes

  • Calling a ViewModel's constructor directly to pass an ID.
  • Setting the ID with an init(id) function after creation, which reruns on rotation.
  • Passing large objects as assisted parameters.

Senior take & follow-ups

Mention both options and when each fits.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why prefer SavedStateHandle for screen IDs?

    Show hint

    It's automatic and survives process death.

  2. What does @AssistedFactory generate?

    Show hint

    An implementation that combines your runtime arguments with injected ones.

Suggest a fix if something here is wrong or out of date.