Jetpack Compose Advanced
๐ฌ Jetpack Compose Advanced
๐ Custom Layouts
Column and Row (Course 2, Chapter 1) cover most needs, but the Layout composable gives full manual control over measuring and placing children โ useful when no built-in layout produces the arrangement needed:
Every child is measured first (asked how big it wants to be, given the available constraints), then placed at specific coordinates โ this two-phase measure/place cycle is exactly what Column and Row do internally, just with their own arrangement logic instead of this example's simple vertical stacking. Reaching for a custom Layout is genuinely rare in practice โ most real UI is built entirely from existing layout composables, so recognizing when it's actually necessary (rather than reaching for it prematurely) matters more than mastering every detail of the API.
๐๏ธ Animations
The simplest Compose animations are animate*AsState functions โ they take a target value and automatically animate toward it whenever that target changes:
animateDpAsState reuses the exact same by property delegation as remember { mutableStateOf(...) } (Course 2, Chapter 1) โ height reads like a plain value, but every recomposition where expanded changes triggers a smooth transition rather than an instant jump.
AnimatedVisibility animates a composable's appearance/disappearance entirely (fade, slide, or a custom combination) rather than animating one property at a time โ the right tool when something is conceptually "there or not there," not just changing size or color.
Compose Animation vs CSS
| CSS | Compose |
|---|---|
| transition: height 0.3s ease; | animateDpAsState(targetValue = ...) |
| @keyframes / a mount-transition library | AnimatedVisibility |
| Coordinated multi-property transition | updateTransition (multiple synchronized animate*AsState-style values) |
๐จ Theming Beyond the Defaults
Course 1 Chapter 8 covered XML themes (themes.xml, colorPrimary). Compose has its own, parallel theming system โ MaterialTheme, typically wrapping the whole app:
Any composable inside MyAppTheme { } can read MaterialTheme.colorScheme.primary instead of hardcoding a color โ the exact same "define once, reference everywhere" idea as Course 1's XML theme attributes, just expressed as Kotlin objects instead of an XML resource. Dark mode support follows the same pattern too โ a separate darkColorScheme(...), chosen based on isSystemInDarkTheme(), replaces Course 1's values-night resource-qualifier folder mechanism with an explicit runtime check.
โก Performance โ Avoiding Unnecessary Recomposition
Course 2 established that Compose recomposes only what reads changed state โ but that guarantee depends on Compose being able to tell whether a composable's inputs actually changed, which isn't automatic for every type:
Stable Types Recompose Efficiently
A data class built entirely from vals (Kotlin Fundamentals Chapter 4) is considered stable โ Compose can compare old vs new by value and skip recomposition if nothing changed. A regular class with vars, or a plain interface, may not offer this guarantee.
remember for Expensive Calculations
A costly computation inside a composable body re-runs on every recomposition unless wrapped in remember(key) { ... } โ the same remember from Course 2, used here purely for caching a derived value, not holding mutable state.
remember(items) { ... } takes items as a key โ the sort only re-runs when items itself changes, not on every unrelated recomposition. key = { it.id } inside LazyColumn's items(...) gives each row a stable identity across reorders/insertions/deletions โ directly the same role React's key prop plays in a list, and DiffUtil's areItemsTheSame played back in Course 1's RecyclerView chapter.
remember { derivedStateOf { someState > threshold } } is a more specialized tool: it recomputes only when its own inputs genuinely change, and โ critically โ only triggers recomposition of composables reading its result when that result actually differs, not every time the underlying state updates. Useful for something like "is the list scrolled past item 5?" derived continuously from a fast-changing scroll position, where recomposing on every pixel of scroll would be wasteful.
๐ป Coding Challenges
Challenge 1: An Animated Toggle
Write a composable ToggleCard(isActive: Boolean) that animates both its background color (animateColorAsState, between two colors) and its elevation/size (animateDpAsState) based on isActive, plus a Button that flips a remembered Boolean state to trigger it.
Goal: Practice combining two animate*AsState calls driven by the same state.
Challenge 2: A Custom MaterialTheme
Define a custom color scheme (at least primary and onPrimary) and wrap a small screen (a Text and a Button) in a MyAppTheme composable using it. Confirm the Button picks up the custom primary color automatically, the same way an unstyled Course 1 Button picked up colorPrimary from an XML theme.
Goal: Practice defining and applying a Compose-native theme.
Challenge 3: remember and key() in a List
Write a composable rendering a LazyColumn of a List<Product>, sorted by price using remember(products) { }, with a stable key = { it.id } on each item. Add a comment explaining what could go wrong (in terms of wasted work or incorrect item state) if either the remember or the key were removed.
Goal: Practice both performance techniques together and reason about why each matters.
Performance techniques like remember and stable types are easy to over-apply "just in case." Android Studio's Layout Inspector and Compose-specific recomposition counts (enabled via a debug flag) show exactly which composables are recomposing and how often โ worth reaching for before assuming where a real performance problem actually is, rather than optimizing blind.
๐ฏ What's Next
Next chapter: Testing โ unit tests, instrumented tests, Espresso, and Compose UI testing.