100 Days of Compose

One hundred days working through the official Jetpack Compose documentation in order, one topic a day. It starts from what you already know, and ends with the eight ideas the whole toolkit keeps circling. Each post takes a symptom, explains the mechanism underneath it, and shows how to prove it on a real device.

Complete 100 of 100 posts February 19, 2024 – December 6, 2026

Contents — page 3 of 4

Day 51Your list rows are different heights because text measurement is honestText height depends on content, font and scale, which makes uniform list rows harder than they look. Line-break strategies, hyphenation and baseline-relative padding are the tools that solve it without fixed heights.6 minDay 52TextFieldState is a document, and edits are transactions on itTextFieldState models text, selection and composition together. Editing through its transaction API preserves undo history and cursor position in ways string replacement cannot, which matters for any non-trivial input.5 minDay 53Your custom font has a fallback, and you didn't choose itA custom font covers a subset of Unicode. Everything outside it falls back, and the fallback decides how your app renders emoji, non-Latin scripts and symbols — usually without anyone checking.6 minDay 54Two lines of autofill are worth more than a week of form polishCompose's autofill is a semantics property on a text field. Declaring content types lets password managers and the platform fill forms correctly, and omitting it is why users abandon sign-up.6 minDay 55contentScale and Modifier.size are answering different questionsImage sizing depends on the modifier constraints and contentScale together. Understanding which one crops, which one fits, and where the aspect ratio comes from resolves most image layout bugs in one pass.5 minDay 56A vector costs CPU once, a bitmap costs memory foreverImageVector is drawing commands executed at render time; ImageBitmap is pixels held in memory. The choice is about scaling fidelity versus per-frame cost, and the common mistakes run in both directions.5 minDay 57A Painter is the thing that knows how big it wants to bePainter draws content and declares an intrinsic size, which is what lets it participate in layout the way a resource does. Writing one is the right answer when drawing needs to be reusable across Image, Icon and modifiers.6 minDay 58Your gradient is measured in pixels, and that's the bugCompose gradients take pixel coordinates, so a hardcoded end point produces a different result on every screen size. Float.POSITIVE_INFINITY and DrawScope.size are the two ways to make a brush relative.6 minDay 59A shadow needs a shape, and Compose won't guess one for youDrawing custom shapes means Path plus DrawScope. Shadows are separate, and Modifier.shadow takes its own shape parameter — omitting it is why rounded cards get square shadows.6 minDay 60Four bytes a pixel is the number that decides whether your app gets killedA bitmap costs width × height × 4 bytes in memory regardless of its file size. Decoding at display size, choosing the right config and budgeting the cache are what keep an image-heavy app from being reclaimed.6 minDay 61Compose has a dozen animation APIs and one decision treeThe Compose animation surface looks large because it is organised by what you're animating rather than by how. Three questions pick the right API, and picking wrong is why animation code gets complicated.6 minDay 62You don't animate from A to B, you declare B and Compose handles the restanimate*AsState animates toward a target that can change at any moment. Understanding it as declarative rather than imperative explains interruption, redirection, and why spring is the right default.6 minDay 63updateTransition is a state machine that happens to animateupdateTransition coordinates several animations driven by one state. It handles multi-state enums, per-property specs via transitionSpec, and child transitions — all with coherent interruption a group of animate*AsState calls cannot give you.5 minDay 64You cannot fade out something that has already left the compositionAn element removed by an if is gone immediately, so an exit animation has nothing to animate. AnimatedVisibility keeps it composed until the exit finishes — and AnimatedContent does the same for swaps.5 minDay 65A shared element transition is one element pretending to be twoShared element transitions match two composables by key and animate between their bounds. Understanding that neither element travels — a third overlay draws the motion — explains the scope requirements and the common failures.6 minDay 66Take the clock away and animations become testableCompose tests idle-wait by default, which makes animated UI either flaky or untested. Disabling autoAdvance gives you frame-level control, turning 'wait and hope' into deterministic assertions at chosen moments.5 minDay 67Compose builds a second tree, and it's the one your users hearEvery Compose UI produces a semantics tree alongside the layout tree. It's what screen readers read, what tests query, and what autofill inspects — so getting it right serves three audiences at once.6 minDay 68One card, four announcements — merging is the fixA composite component produces one semantics node per child by default, so a card becomes four swipes. mergeDescendants, clearAndSetSemantics and invisibleToUser control the shape of the tree.6 minDay 69Reading order is layout order, until your layout stops matching your meaningScreen readers traverse in layout order, which breaks when a floating action button, a sticky header or a two-column layout puts visual priority somewhere else. Traversal groups and indices reorder without moving anything.6 minDay 70Automated checks find the missing labels. They can't tell you the label is wrong.Compose's accessibility checks catch touch targets, contrast and missing labels automatically. The failures they cannot detect — wrong labels, bad order, gesture-only actions — need a person, and knowing the split makes both cheaper.6 minDay 71Every gesture in Compose is a coroutine reading a stream of eventspointerInput gives you a coroutine scope over raw pointer events. Understanding that gestures are suspending loops rather than callbacks explains the whole API, including why the key parameter matters more than it looks.6 minDay 72clickable does six things, and detectTapGestures does oneModifier.clickable adds accessibility semantics, focus, keyboard activation, ripple and a minimum touch target on top of tap detection. Reaching for detectTapGestures instead silently drops all of it.5 minDay 73Swipe-to-dismiss is a state machine with anchors, not a drag handlerHand-rolled swipe gestures compare a distance threshold and ignore velocity, which is why a fast flick feels wrong. AnchoredDraggableState models discrete positions and settles between them using both distance and speed.6 minDay 74Pinch, pan and rotate arrive together, and the centroid is what ties themMulti-touch gestures produce pan, zoom and rotation simultaneously. The centroid — the point between the fingers — is what makes the transform feel anchored, and ignoring it is the most common photo-viewer bug.6 minDay 75Nested scroll is a negotiation, and there are four moments to interveneNestedScrollConnection gives parents four chances to consume scroll deltas around a child. Choosing the wrong one is why collapsing headers collapse late, and why same-axis nesting misbehaves.6 min