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.

PracticeCovered in
State hoisting, unidirectional data flowState Hoisting, Unidirectional Data Flow
remember vs. rememberSaveablederivedStateOf & rememberSaveable
StateFlow over callbacks/LiveDataStateFlow & collectAsState, Room Database with Compose
Recomposition stabilityPerformance & Stability
Testable from the startCompose UI Testing
Congratulations — you've completed the Jetpack Compose track! You've gone from your first composable all the way through Material 3 theming, navigation, ViewModel-driven architecture, side effects, animations, custom layouts, Room, Retrofit, Paging 3, UI testing, and performance tuning. If you'd like to see the same RecipeBox concepts built the traditional View-based way, Android (Java) and Android (Kotlin) are waiting as complementary tracks — and now that Compose is second nature, the interop lesson you just finished means you're equipped to bring Compose into an existing View-based app too, one screen at a time.