Theming, previews and tests
Lesson 5 of 51 min read
One place for colour and type, previews that replace rebuilds, and the three tests worth writing.
This is the lesson that separates a tutorial app from a shipped one.
Theme in one place
@Composable
fun AppTheme(dark: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = if (dark) DarkColors else LightColors,
typography = AppTypography,
content = content,
)
}
Then never hard-code a colour in a screen. Use MaterialTheme.colorScheme.primary and MaterialTheme.typography.titleMedium. The payoff arrives the day someone asks for dark mode or a brand refresh: one file changes, not ninety.
Previews instead of rebuilds
@Preview(name = "Light")
@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES)
@Composable
private fun ProductRowPreview() {
AppTheme { ProductRow(sampleProduct) }
}
Preview every state, not just the happy one: loading, empty, error, long text, and a large font scale. A stateless composable makes this trivial — which is the practical reason state hoisting pays off.
Accessibility, the two-minute version
contentDescriptionon meaningful images;nullon decorative ones.- Touch targets at least 48 dp.
- Check at 200% font scale — fixed
heighton a text row is the usual casualty.
The three tests worth writing
@Test
fun shows_empty_state_when_there_are_no_items() {
composeRule.setContent { AppTheme { FeedScreen(FeedUiState(items = emptyList())) } }
composeRule.onNodeWithText("Nothing here yet").assertIsDisplayed()
}
- ViewModel unit tests — the logic, on the JVM, fast.
- A screenshot or state test per screen — catches layout regressions cheaply.
- One or two journey tests — the flow that earns money.
The usual mistake: testing that a Text exists with the exact production copy. Marketing changes a word and your test fails for no reason. Assert on behaviour and test tags, not prose.
