100 days of Compose: the eight ideas the other ninety-nine posts kept circling
A hundred days of Jetpack Compose, written by following the official documentation end to end. The APIs are many and the ideas are few; here are the eight that kept recurring, and an honest account of what the exercise missed.

Day 100. Ninety-six posts written by walking the official Compose documentation in order,
one topic at a time, from setup-and-bom to glance-widgets.
The thing I didn't expect: the API surface is enormous and the number of ideas is small. By about Day 40 I was mostly writing "this is Day 12's argument, in a new place." That wasn't padding — it's genuinely how the framework is built, and noticing it is the difference between memorising a library and understanding one.
Here are the eight ideas that kept coming back, then an honest account of what this method missed.

1. Declare the destination, not the path
Compose removed the ability to reach into the UI and change it (Day 5). Everything downstream is a consequence.
A dialog isn't showDialog(), it's dialog is ConfirmDelete — a fact that's true until it
isn't (Day 39). An animation isn't animate(from, to), it's a target the value converges
on, which is why interruption works (Day 62). A swipe isn't a distance compared to a
threshold, it's a choice between named anchors (Day 73).
Each time, the payoff is the same: the hard cases — interruption, rotation, reversal, process death — stop being special cases, because there was never a plan to invalidate.
2. The read site is the subscription site
Reading a value is what subscribes a scope to it. That single sentence explains
recomposition (Day 7), the three phases (Day 10), why Modifier.offset { } takes a lambda
(Day 82), and most of the performance pillar.
It also produces the highest-leverage habit in the whole series: if a value changes every frame, read it in a lambda. Moving one read from composition into layout is two braces and frequently several milliseconds at P99.
3. Identity has to be declared, because the default is positional
Compose knows "the third slot", not "the row belonging to Ada". That's efficient and it's wrong whenever the data moves.
The same bug appears as expansion state on the wrong list row (Day 8), scroll jumps in a
LazyColumn (Day 23), a shared element that silently doesn't animate (Day 65), and taps
going to the wrong item after slot reuse (Day 71). It's getItemId under a new name
(Day 90), and key is the answer every time.
Any time state seems to attach to the wrong thing, the question is whether you told the framework what identity means.
4. Ask the system for the measurement
The most repeated correction in the series, and the one with the widest reach.
isTablet() was never asking about tablets; it was asking about available room, badly
(Day 29). A 64dp constant for an app bar is a measurement the scaffold already has
(Day 35). A gradient with endX = 1080f is a screen width baked in (Day 58). A 24dp status
bar padding is a number that varies per device and per navigation mode (Day 95).
The tell is always the same: a number in the source that came from measuring one device. There's a system-provided alternative in every case, and Android 15's mandatory edge-to-edge is the platform converting that latent assumption into an immediate failure.
5. Name the role, not the value
headlineSmall survives a redesign; 24.sp doesn't, and there's no way to tell which
24.sp meant "headline" and which meant "slightly large body" (Day 46).
The same argument holds for colour — surfaceContainer versus 0xFFF5F5F5 (Day 44) — and
for shape (Day 47). It's what makes "keep the brand, adapt the chrome" expressible at all
when dynamic colour arrives (Day 45), because primary and surface are different names
rather than two hex values.
6. Model the states; don't derive booleans from them
A sealed UiState makes "loading with an error showing" unrepresentable rather than a bug
to remember not to write (Day 12). A Transition over an enum is exhaustive, can vary its
timing per edge, and coordinates every property that depends on it — none of which a set of
extracted booleans can do (Day 63). Anchored dragging turns "where did the finger stop"
into "which state did they choose" (Day 73).
Naming the discrete thing is repeatedly what turns a pile of conditionals into a small amount of configuration.
7. Scope is a design decision, not an implementation detail
Where a value lives is the question the state pillar spent eleven days on, and it keeps reappearing.
Hoist to the lowest common ancestor — the trigger is a second reader, not a rule to apply
preemptively (Day 11). A ViewModel is for state that outlives the screen, not a default
destination (Day 12). A CompositionLocal is for the environment, never for
dependencies (Day 15). A theme scopes to a subtree; a component style scopes to one call,
and using one mechanism for the other is why "danger button" can't be a CompositionLocal
(Day 49).
8. Describe accurately and you get behaviour you didn't implement
Declaring a contentType on a text field gets you a working password manager (Day 54).
Setting Role.Switch gets a correct screen-reader announcement. A LinkAnnotation gets
link semantics. Merging a card's semantics correctly gets a better announcement, a simpler
test selector and a single Switch Access target (Day 68).
This is the one I'd most want a reader to take away, because it inverts how accessibility usually gets framed. The semantics tree is the tree your tests query (Day 84) — so writing tests against text and descriptions forces the semantics to be right, and the accessibility work and the testing work turn out to be the same work.
What this method missed
Two things, and being honest about them matters more than the summary above.
Documentation order is not learning order. The docs are organised by API surface, so I wrote about pickers on Day 42 and about testing on Day 85 — even though a real project needs the testing setup first and may never need a date picker. Anyone learning Compose should read Days 5–17 (state and architecture), then 78–87 (performance and testing), then whichever component pillar their screen actually needs. The middle sixty days are reference material, not a curriculum.
Working from documentation means writing about the happy path. The docs describe what
an API does; they rarely describe what it does at 2× font scale, in Arabic, on a
three-year-old phone with a full memory cache. I tried to put those cases in every post —
the @Preview(fontScale = 2f), the gesture-versus-button navigation check, the mid-range
device — but they're the parts I had to supply, and they're where real bugs live. A series
written from bug reports rather than from documentation would have a different and probably
more useful shape.
There's a third thing I'd flag as a caveat rather than a failure: performance advice has
a version. Day 79 laid out the stability model most Compose writing is based on, and
Day 80 explained that strong skipping substantially changed it. A lot of the folklore in
circulation — annotate every model class with @Immutable — is now unnecessary and
occasionally harmful. If a technique's justification requires naming a compiler flag, check
the flag before applying the technique.
The advice I'd actually give
If you're starting with Compose:
Learn the state model properly before anything else. Days 7 through 17. Everything else is easier afterwards, and skipping it produces code that works and can't be maintained.
Write the stateless content composable. Data in, callbacks out. It makes the component previewable (Day 92), testable (Day 85), reusable, and it's the single structural decision that most improves a Compose codebase.
Don't optimise until you've measured, and measure the phase. Most screens need none of Days 78–83. Performance work without a measured problem is complexity with a good reputation.
Do the accessibility pass as you build. Twenty minutes per screen while you're in it, five minutes thereafter. It's dramatically cheaper than an audit later, and it improves your tests for free.
Put the conventions in lint, not in a document. A convention that isn't enforced is a preference (Day 94).
Closing
The most useful sentence I can offer after a hundred days is the one Day 66 landed on and Day 83 repeated: the APIs are many, the ideas are few.
Compose looks large because its surface is large — hundreds of composables, dozens of modifiers, four animation APIs, three inset groups. But nearly every one of them is one of the eight ideas above, expressed for a particular case. Once the idea is clear, the API stops needing to be memorised, because you can usually guess what it should be called and what parameters it should take.
That's what a hundred days of reading documentation actually bought: not a memorised library, but a small enough model that the library became guessable.
Thanks for reading. The whole series is indexed at blogs.mobilebytesensei.com, and Day 1 starts at the beginning.
Day 100 of a 100-day series on Jetpack Compose, working through the official documentation in order. Source: Jetpack Compose documentation.