ViewModel & LiveData
Back in the lifecycle lesson, you saw that rotating the device destroys and recreates an
Activity or Fragment by default. onSaveInstanceState() can rescue a handful of small
values, but it's the wrong tool for something like TrailLog's current in-memory list of trails —
you don't want to serialize an entire list into a Bundle on every rotation just to survive it.
ViewModel exists to solve exactly this problem.
The Problem: Data Held in the UI Layer Doesn't Survive
If TrailListFragment keeps its list of trails in a plain property, that list is gone
the instant the Fragment is recreated — the user rotates their phone and the screen briefly
flashes empty before reloading. A ViewModel is a class specifically designed to
survive configuration changes automatically, scoped to the lifecycle of its
owning Activity or Fragment rather than to the specific instance that gets destroyed and rebuilt.
The system keeps the same ViewModel instance alive across the rotation and simply
hands it back to the new Fragment instance.
Worked Example: TrailListViewModel
class TrailListViewModel : ViewModel() {
private val _trails = MutableLiveData<List<Trail>>(emptyList())
val trails: LiveData<List<Trail>> = _trails
fun loadTrails(loadedTrails: List<Trail>) {
_trails.value = loadedTrails
}
}
A Fragment obtains its ViewModel through the by viewModels() Kotlin property
delegate, rather than constructing it directly:
class TrailListFragment : Fragment() {
private val viewModel: TrailListViewModel by viewModels()
// ...
}
The by keyword here is Kotlin's property delegate syntax — instead
of viewModel holding the value directly, viewModels() hands back a small
helper object that knows how to create (or, after a rotation, simply retrieve) the correct
TrailListViewModel instance scoped to this Fragment, and every read of
viewModel goes through that helper. It's a genuinely elegant piece of Kotlin: what
would otherwise be several lines of manual ViewModelProvider lookup collapses into
one property declaration.
LiveData: Observing Data That Knows About the UI's Lifecycle
A plain callback has no idea whether the screen listening to it is still alive — if
TrailListFragment is destroyed while a background load is still running, a raw
callback firing afterward tries to update views that no longer exist, which is a common source of
crashes and memory leaks in hand-rolled async code. LiveData is an observable data
holder that is lifecycle-aware: it only delivers updates to observers that are currently
in an active state, and it automatically stops delivering them — and cleans up the subscription —
once the observing Fragment or Activity is destroyed. That single property eliminates an entire
category of "updated a dead view" crashes without any manual unsubscribe code.
Worked Example: Observing the Trail List
TrailListViewModel above already exposes trails as
LiveData<List<Trail>>. The Fragment observes it like this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.trails.observe(viewLifecycleOwner) { trails ->
trailAdapter.submitList(trails)
}
}
Passing viewLifecycleOwner — not the Fragment itself — is important here: it ties the
observation to the Fragment's view lifecycle rather than the Fragment object's own
longer lifecycle, which matters because, as covered in the Fragments lesson, a Fragment's view can
be destroyed and recreated while the Fragment instance itself lives on.
MutableLiveData Internally, Plain LiveData Externally
Look again at the two properties on TrailListViewModel: a private
_trails of type MutableLiveData, and a public trails of the
plain, read-only LiveData type, simply assigned from the mutable one. This is a
genuinely common and worthwhile encapsulation pattern — only the ViewModel itself can call
.value = to change the data, while anything observing from outside, like the
Fragment, can only read it. Without this split, any Fragment holding a reference to a mutable
LiveData could push its own values into it, defeating the entire point of the ViewModel owning
the data.
Flow APIs. Flow vs LiveData, later in this track, introduces StateFlow as LiveData's more modern, Kotlin-native alternative — and explains when you'd reach for each.