Using Android architecture components with MVVM
A practical MVVM design uses Android architecture components to divide an app among its user interface, retained state, and data sources. Screens can react to lifecycle changes without owning database queries, HTTP calls, or business rules.
The pattern suits Kotlin or Java projects, phone and tablet layouts, and apps backed by web services or local storage. ViewModel, LiveData, Room, and lifecycle-aware classes are now commonly grouped under Jetpack, but MVVM remains an effective way to connect them.
Place MVVM responsibilities in the right layer
An Activity or Fragment forms the UI layer. It renders XML or Compose state, supplies accessibility semantics, handles navigation, and forwards clicks and input. It should not launch repository calls or preserve downloaded records merely because the user rotated the device.
A ViewModel owns screen state and coordinates user intentions. It exposes immutable state through LiveData or StateFlow, invokes a repository, and maps domain failures into loading, empty, content, or error states. The framework keeps it alive while configuration changes recreate the screen, then clears it when that screen is finished.
The model layer holds domain objects, repository contracts, and data sources. Room, an HTTP client, and preferences can sit behind that contract. Dependencies point from UI to ViewModel to repository, so a fake repository can replace network and storage during a fast unit test.
Build a repository around one source of truth
Build the repository around a stable domain API rather than exposing Retrofit, cursors, or SharedPreferences. A cache-first flow can emit Room data immediately, refresh through the remote service, and insert a fresh response locally. The UI then subscribes to one observable source and receives consistent results.
That matters for Australian mobile use. A passenger moving through a Melbourne tunnel or along Sydney rail may briefly lose connectivity, making cached content and a clear retry state more useful than an empty error screen. Treat offline data as valid only when freshness rules allow it.
Run blocking work on an injected dispatcher, propagate coroutine cancellation, and convert expected failures into domain results. This also supports Australians on prepaid plans with capped data: query limits, image sizing, and cache policy belong in the data layer instead of being repeated across every screen.
Retain screen state with a ViewModel
Create one ViewModel for the screen’s retained state and initialise it with constructor-injected repository dependencies. Calling a request from an observer can repeat work after rotation; place the initial load in initialisation or an explicit user action, then let the ViewModel coordinate it.
For a focused implementation, the tutorial on retaining state across rotation demonstrates the basic ViewModel lifecycle pattern. The key idea is to separate object lifetime from view lifetime: XML views may be destroyed and rebuilt while the ViewModel instance remains.
A ViewModel does not survive process death. Store a small recovery value in SavedStateHandle, while Room remains the source for larger records. Never place access tokens or unrestricted personal data in saved instance state, and prefer an ordinary ViewModel over AndroidViewModel unless an application dependency is essential.
Deliver lifecycle-aware state to the UI
LiveData is lifecycle-aware: an Activity observes while active, and a Fragment uses viewLifecycleOwner so observers disappear with its view. StateFlow is another valid choice, but collection should still stop when the UI is not started. Either option keeps resources tied to screen behaviour rather than manual lifecycle code.
Represent durable screen conditions as a sealed UiState with content, loading, empty, and error branches. Navigation and snackbars are one-off events, so give them separate handling; recreating a view must not replay a payment or sign-out action.
A compact comparison shows where responsibilities belong:
| Responsibility | ViewModel | Repository | Activity or Fragment |
|---|---|---|---|
| Screen state | Exposes immutable state | Supplies domain data | Observes and renders it |
| User action | Accepts intent and updates state | Executes data operations | Forwards user input |
| Data access | Calls a repository contract | Coordinates remote and local sources | Avoids direct data-source calls |
| Failure policy | Maps errors into UI state | Normalises data failures | Presents recoverable messages |
Connect user actions to state changes
With view binding, each control forwards an intention rather than performing work. A submit button can call viewModel.submitSearch(query); the ViewModel validates the input and requests new state, while the observer renders the result. Keep Android navigation decisions in the UI unless the state explicitly declares the next destination.
Model inputs once, then publish state sufficient to rebuild the whole screen after rotation. This prevents controls from drifting out of sync and gives automated tests a single assertion surface. For controls backed by adapters, submit list differences rather than mutating a shared adapter behind the ViewModel.
Create display strings with Locale("en-AU") and day-first dates such as 04/07/2026, while retaining ISO dates or timestamps in the model. Put those rules in resources or formatters, not scattered string concatenations, so Australian English and the selected user locale remain consistent.
Test MVVM boundaries and user journeys
Test ViewModels as plain Kotlin classes with a fake repository and a controlled coroutine test dispatcher. Assert that valid input produces content, invalid input produces a validation state, cancellation is propagated, and a second observer receives the current value without another request.
Test repositories separately with an in-memory Room database, mocked web responses, and database migrations. Espresso or UI Automator can verify Android-specific wiring such as rotation, touch targets, accessibility labels, and screen restoration. Unit tests should remain fast, while instrumentation tests cover behaviour requiring a real Android runtime.
MVVM does not replace checks across a wider system. For a companion booking or administration website used by Australian operations teams, a tool such as end-to-end web checks can verify critical browser journeys. It complements native Espresso coverage by protecting the web service and its pages while Android tests protect behaviour inside the app.
Apply MVVM in Australian releases
Architecture supports Australian privacy obligations as well as clean code. Where the Privacy Act 1988 and Australian Privacy Principles apply, document the purpose, collection, use, retention, and access path for personal information. Repository boundaries make that inventory clearer, while UI state should contain only what the current screen genuinely needs.
Commercial email or SMS requires consent or another documented lawful basis under the Spam Act 2003, together with a clear unsubscribe path. A ViewModel can expose consent state and validation, but it should not invent the legal basis. Product, engineering, and compliance owners should agree on the message purpose and supporting evidence before release.
For the local market, present AUD subscriptions through Google Play with accurate renewal terms and make cancellation and restoration paths obvious. The ACCC and Australian Consumer Law make vague savings claims or hidden subscription conditions risky. Architecture helps only when the displayed state and billing implementation tell the same truthful story.
- Keep Activities and Fragments limited to rendering, accessibility, navigation, and forwarding user intent.
- Inject repository interfaces so ViewModels can be tested without Android context or network access.
- Expose immutable UI state and separate one-off events so rotation cannot duplicate actions.
- Save only compact recovery values in
SavedStateHandle; keep bulk and sensitive data in appropriate storage. - Test loading, empty, error, offline, rotation, and process-recreation paths at the layer that owns them.
- Check Australian formatting, consent evidence, privacy retention rules, and AUD billing details during release verification.