Working with ViewModel and LiveData in modern Android apps

Two Jetpack components have quietly become the backbone of every serious Android app built in Australia, from the checkout flow at Woolworths to the in-store scanners at Bunnings. ViewModel and LiveData solve a problem that used to frustrate local developers working on Telstra- and Optus-issued tablets: how do you keep UI state alive across configuration changes, process death, and flaky 4G handoffs on a Sydney-to-Melbourne V/Line train.

This guide walks through what each component does, how to wire them into a Gradle project, and when it makes sense to prefer one over the other. Engineers in Brisbane and Perth tend to introduce juniors to both at the same time, and most Melbourne code reviews reference the same canonical set of patterns.

What ViewModel and LiveData actually do

ViewModel is a scope-bound holder for UI state that survives recreation. Rotate the phone in a rideshare app on a Gold Coast highway and the ride counter keeps ticking. Close the app to take a call and the search results are still there when you return. The instance is tied to a ViewModelStore that lives longer than any single Activity or Fragment, which is exactly what you want for anything heavier than a simple form.

LiveData is a lifecycle-aware observable data holder. It only delivers updates to observers that are in an active state, which means a Fragment in the background will not receive a callback that triggers a UI update it cannot render. This single property eliminates a whole class of crashes that used to plague Australian fintech apps handling ANZ Pay or CBA notifications while the user switched between banking and a Chrome tab.

Wiring dependencies in Gradle

Modern Android development in Melbourne agencies like REA Group and Canva typically sits on Kotlin with the AndroidX lifecycle artifacts. Drop these into the module-level build.gradle.kts:

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.activity:activity-ktx:1.9.3")
implementation("androidx.fragment:fragment-ktx:1.8.5")

If you build with Java instead, swap the ktx artifacts for their non-Kotlin counterparts. Sydney teams still maintaining legacy CBA-internal apps often need both, and that is fine because ViewModel and LiveData speak Java natively. Pin the version numbers explicitly so a Gradle refresh on a lab machine at the University of Melbourne does not pull in an incompatible snapshot from Maven Central.

Creating a ViewModel for your screen

The cleanest pattern is a class that extends ViewModel and exposes MutableLiveData for anything the UI needs to render. Picture a small weather screen showing conditions for Adelaide, Darwin, and Hobart:

class WeatherViewModel : ViewModel() {
    private val _conditions = MutableLiveData<List<CityWeather>>(emptyList())
    val conditions: LiveData<List<CityWeather>> = _conditions

    fun load() {
        viewModelScope.launch {
            val fetched = repository.fetchAll()
            _conditions.value = fetched
        }
    }
}

viewModelScope ties the coroutine to the ViewModel lifecycle, so cancelling the screen also cancels the network call. That is a quiet but important win for Optus customers on capped regional plans, where a runaway request can chew through the monthly allowance before lunchtime.

Instantiate the ViewModel through the Activity or Fragment rather than constructing it directly, otherwise you lose the lifecycle integration. The by viewModels() delegate from activity-ktx handles this automatically and is the pattern most Gold Coast agency codebases have standardised on.

Observing LiveData from a Fragment

Observing is where lifecycle awareness earns its keep. Inside an onViewCreated callback:

viewModel.conditions.observe(viewLifecycleOwner) { list ->
    adapter.submitList(list)
}

Passing the fragment's viewLifecycleOwner rather than this matters when the view is recreated, for example after navigating back from a details screen. Brisbane engineers have debugged late-callback issues that traced back to passing the Fragment itself and ending up with two observers alive at once after a back-stack pop.

LiveData also plays nicely with transformations. map, switchMap, and MediatorLiveData let you derive one stream from another without manually subscribing and unsubscribing. A practical example is showing the user's local time only when the observed city selection changes — switchMap cancels the previous emission and forwards only the latest one, which keeps an Adelaide-to-Perth timezone selector responsive even on a shaky NBN connection.

LiveData versus StateFlow and other reactive options

As Australian engineering teams modernise, the question of LiveData versus Kotlin Flow or StateFlow comes up in nearly every code review. Both approaches work, but the ergonomics differ in ways that matter for shipping schedules.

Aspect LiveData StateFlow / SharedFlow
Lifecycle awareness Built in via LifecycleOwner Requires repeatOnLifecycle or collectAsStateWithLifecycle
Initial value Optional, nullable by default StateFlow requires an initial value
Backpressure Not applicable, push only Configurable, supports buffering and conflation
Java interop First class via observe methods Needs scope wrappers, less ergonomic
Test surface InstantTaskExecutorRule plus getOrAwaitValue runTest plus Turbine, more powerful but heavier

For pure Kotlin codebases inside a Canva-style Compose-first environment, StateFlow is usually the better fit. For mixed Java and Kotlin shops, especially those still maintaining ATO-style legacy modules, LiveData remains pragmatic and keeps the team moving through quarterly releases.

Take it into your own project

A complete screen reads from the ViewModel, the ViewModel reads from a repository, and the repository handles network and database access through Room or Retrofit. LiveData or StateFlow threads through all three layers with the same shape, which keeps the mental model small. Drop the snippets above into a starter project on your next train ride from Central to Hurstville and you will have a working feedback loop by the time you reach Kogarah.

Pull the official Android Developers samples for the canonical reference, then layer in curated walkthroughs from the android tutorial point catalogue for the kind of step-by-step migration notes that Atlassian engineers in Sydney rely on when onboarding new hires across multiple product squads.