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 1 of 4

Day 1Analyse your Android knowledge before you learn anything newBefore learning a new toolkit, work out what you already know. Four questions that decide where to start, applied to Jetpack Compose.3 minDay 2What is Jetpack Compose?XML made you invalidate the UI by hand. Compose recomposes only the nodes whose data changed — and that one difference reorders how you build a screen.5 minDay 3Basics of Jetpack ComposeWhat a composable function actually is, the three layout blocks every screen is made of, and how to preview one without running the app.3 minDay 4Compose has two version problems, and the BOM only solves oneThe Compose BOM aligns runtime artifacts. The Kotlin/Compose compiler is a separate alignment problem, and mixing the two up produces errors that look like your code is wrong when it isn't.6 minDay 5Thinking in Compose: you can't reach into the UI any more, and that's the pointIn Views you fix UI by reaching in and mutating it. Compose removes that ability entirely. Understanding why the removal is the feature is what makes the rest of Compose make sense.6 minDay 6What @Composable actually does to your function@Composable is a compiler plugin that adds hidden parameters to your function. Once you know what it adds, the rules about calling context, skipping and recomposition stop being arbitrary.6 minDay 7Recomposition doesn't re-run your screen. It re-runs the scopes that read what changed.Compose does not re-run your whole screen when state changes. It re-runs the smallest restartable scopes that read the changed value. Once you can see those scopes, recomposition stops being mysterious.6 minDay 8A composable's lifecycle is three events, and none of them is onDestroyComposables have a lifecycle, but it is nothing like a View's. Three events — enter, recompose, leave — and the identity rules that decide which one you get.6 minDay 9Your composable ran four times. So did your network call.A network call written directly in a composable fires an unpredictable number of times. LaunchedEffect, DisposableEffect and SideEffect each define a different notion of when work should happen.6 minDay 10Composition, layout, drawing — and why reading a value in the wrong one costs framesCompose renders in three phases. Where you read state decides which phase invalidates, and moving a read from composition into layout or draw is the single highest-leverage performance change available.6 minDay 11State hoisting is one question: who else needs to know?State hoisting means moving state to the lowest common ancestor of everything that needs it. The trigger is a second reader — not a rule to apply preemptively.6 minDay 12The ViewModel is not the default place for state. It's the place for state that outlives the screen.Hoisting to a ViewModel is a different decision from hoisting to a parent composable. The dividing question is lifetime, and getting it wrong in either direction has a specific cost.6 minDay 13Six parameters deep and none of them are used hereCorrect hoisting can still produce an unmaintainable codebase. The four failure modes that show up once a screen grows past a few components, and the fixes for each.6 minDay 14rememberSaveable survives what remember doesn't — and it isn't a bigger rememberremember dies on rotation. rememberSaveable survives rotation and process death by writing into the saved instance state Bundle — which has limits, costs, and a custom-type story worth knowing before you need it.6 minDay 15CompositionLocal is not dependency injection, and using it that way hurts laterCompositionLocal passes values down the composition without parameters. That invisibility is the feature and the danger: three tests for when it is the right tool, and what to use instead when it isn't.5 minDay 16The UI layer has two halves, and merging them is why screens become untestableCompose's recommended architecture is three layers with one-directional dependencies. The split inside the UI layer — elements and state holders — is the one that decides whether a screen can be tested without a device.6 minDay 17Not every state holder is a ViewModelCompose has two kinds of state holder: plain classes that live in the composition, and ViewModels that outlive it. Knowing which to reach for is what keeps UiState classes from growing to twenty fields.6 minDay 18Column, Row, Box — and the single-pass rule that makes them fastCompose's layout system measures every child exactly once. Understanding that single-pass guarantee explains Column, Row and Box, and why some things you could do in View layouts are deliberately impossible.6 minDay 19A Modifier is a linked list, and that explains almost everything about itModifier looks like a fluent builder but is an ordered immutable linked list. Knowing its actual shape explains chaining, reuse, the modifier parameter convention, and why some combinations do nothing.6 minDay 20padding().background() and background().padding() are not the same pictureModifier order changes rendered output because each element wraps the remainder of the chain. The outside-in reading rule resolves padding, background, clickable ripple bounds and clip behaviour in one go.6 minDay 21Why fillMaxWidth() sometimes does nothingCompose sizing confuses people because Constraints is a min/max range rather than a value. Each sizing modifier transforms the range in a specific way, and reading them that way makes layout deterministic.5 minDay 22Write a custom modifier only after you've ruled out a functionMost custom modifiers should be plain extension functions returning a chain. Modifier.Node is for elements that need their own state or draw/measure participation, and it replaced composed { } for good reasons.6 minDay 23LazyColumn is not a faster Column, and items(key = …) is not optionalLazyColumn composes only visible items and reuses slots as you scroll. Without a stable key that reuse attaches state to the wrong rows — the same positional-identity bug as Day 8, at scale.6 minDay 24A grid is not a list with two columnsLazyVerticalGrid adds cell strategy and span control on top of lazy lists. GridCells.Adaptive removes most breakpoint code, and span sizing is what lets headers live in the same grid as items.6 minDay 25HorizontalPager is a lazy list that snaps — and PagerState is where the bugs liveHorizontalPager brings paging into Compose with no external dependency. The interesting surface is PagerState — currentPage versus settledPage, scroll versus animateScrollToPage, and how to sync tabs without a loop.6 min