Jetpack Compose Best Practices
This is the last lesson of the 57-lesson Jetpack Compose track — no new API here, just a checklist that ties everything from your very first composable through Room, Retrofit, Paging 3, testing, and performance back together. Every item below was covered in depth somewhere earlier in this track; treat this page as the summary you come back to when starting a new screen, not as new material to learn from scratch.
Keep Composables Small and Stateless — Hoist State Up
A composable that reads and writes its own state directly is hard to reuse, hard to preview, and
hard to test in isolation. State Hoisting covered the fix:
a composable should, wherever practical, take its current value and an onValueChange
callback as parameters instead of owning that state itself — the caller decides where the real state
lives. Unidirectional Data Flow is the same idea
applied at the screen level: state flows down from a ViewModel, events flow back up as function
calls, never the other way around.
Don't Duplicate State — Hoist It, Don't Copy It
When the same piece of information is needed in two places, resist the urge to keep two separate
mutableStateOf holders in sync by hand — that's exactly how a RecipeBox screen ends up
showing a stale filter or a recipe count that doesn't match the list beneath it. Hoist the state to
the nearest common owner (often a ViewModel) and have both places read from that single source, as
MVVM Architecture in Compose laid out.
Use remember and rememberSaveable Correctly
remember survives recomposition but not configuration changes or process death;
rememberSaveable (covered alongside derivedStateOf in
derivedStateOf & rememberSaveable)
survives both, at the cost of only working with types that fit in a Bundle. Reach for
rememberSaveable for anything the user would be annoyed to lose on rotation — an
in-progress form field is the classic example — and plain remember for state that's
fine to reset, like whether a tooltip is currently showing.
Prefer StateFlow + collectAsStateWithLifecycle Over Raw Callbacks
StateFlow & collectAsState and this track's
Room and Retrofit lessons all leaned on StateFlow exposed from a ViewModel rather than
plain callbacks or LiveData — it composes cleanly with coroutines, works naturally with
stateIn for turning a Room Flow into UI state, and reads the same way
whether the data came from a database, a network call, or Paging 3. In a real project,
collectAsStateWithLifecycle() (from the lifecycle-runtime-compose artifact) is worth
preferring over the plain collectAsState() used throughout this track for simplicity —
it automatically stops collecting while the screen isn't visible, avoiding wasted work and state
updates delivered to a UI that isn't even on screen.
Watch for Recomposition Stability
Performance & Stability covered the concrete
failure mode: a raw mutable List parameter, or a lambda re-created on every parent
recomposition, can silently defeat Compose's ability to skip work it doesn't need to redo. Prefer
data classes with val properties, hoist lambdas out of loops instead of
recreating them per item, and use the Layout Inspector's recomposition counts to confirm a fix
actually reduced unnecessary recompositions rather than just assuming it did.
Test With Semantics and testTags From the Start
Compose UI Testing showed Modifier.testTag(...) as
the reliable way to find a node that has no unique visible text. Adding tags to interactive elements
(buttons, text fields, key status messages) while you're first building a screen costs almost
nothing; retrofitting them onto a screen full of composables after the fact — once you actually need
a test and discover nothing is findable — costs considerably more, and it's easy to miss a few
elements a rushed retrofit should have tagged.
| Practice | Covered in |
|---|---|
| State hoisting, unidirectional data flow | State Hoisting, Unidirectional Data Flow |
| remember vs. rememberSaveable | derivedStateOf & rememberSaveable |
| StateFlow over callbacks/LiveData | StateFlow & collectAsState, Room Database with Compose |
| Recomposition stability | Performance & Stability |
| Testable from the start | Compose UI Testing |