val prevents reassignment of a reference; var permits it. This does not automatically make the referenced object immutable, but choosing val by default dramatically reduces the number of states an Android feature can enter.
Practical guidance
- Use val for dependencies, state streams and local calculations.
- Keep mutable state private and expose read-only StateFlow or List interfaces.
- Use copy on data classes to produce a new state value.
- Reserve var for lifecycle-bound handles or genuinely evolving implementation details.
Working example
data class ProfileState(val loading: Boolean = false, val name: String = "")
private val _state = MutableStateFlow(ProfileState())
val state: StateFlow<ProfileState> = _state.asStateFlow()
fun load() {
_state.update { it.copy(loading = true) }
}
Common mistakes
- val list = mutableListOf() still allows list mutation.
- Public var properties make invariants difficult to enforce.
- Changing everything to immutable collections is not useful if callers still receive mutable references.
Key takeaway
Prefer val and localize mutation. A small, explicit mutation boundary makes ViewModels easier to test and concurrent code easier to reason about.
