Writing Compose UI with the agent
Compose is relatively friendly territory for an agent: the code is declarative, concentrated in one file, and quickly checked with @Preview. But there are typical mistakes, and they compile — meaning only review and tests catch them:
- Broken state hoisting — the Composable keeps its state inside, so it can't be reused or tested.
- Recomposition problems — computation directly in the Composable body without
remember. - Modifier not threaded properly — not accepted as a parameter, or not applied first in the chain.
- Lifecycle-unaware collection —
collectAsStatekeeps collecting in the background.
// WHAT THE AGENT OFTEN WRITES (compiles, but has problems)
@Composable
fun OrderScreen(viewModel: OrderViewModel) {
val state by viewModel.uiState.collectAsState() // not lifecycle-aware
var filter by remember { mutableStateOf("") } // state trapped inside
val sorted = state.orders.sortedBy { it.date } // re-sorts on every recomposition
Column {
// no modifier parameter — the caller cannot control sizing
sorted.forEach { OrderRow(it) }
}
}
// AFTER REVIEW
@Composable
fun OrderScreen(
viewModel: OrderViewModel,
modifier: Modifier = Modifier,
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
OrderScreen(
state = state,
onFilterChange = viewModel::onFilterChange,
modifier = modifier,
)
}
// The stateless version: testable and previewable
@Composable
fun OrderScreen(
state: OrderUiState,
onFilterChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val sorted = remember(state.orders) { state.orders.sortedBy { it.date } }
Column(modifier = modifier) {
sorted.forEach { OrderRow(order = it) }
}
}The typical gap. `collectAsStateWithLifecycle` comes from the `androidx.lifecycle:lifecycle-runtime-compose` artifact and ties collection to the lifecycle.
| What to state in the task | Why |
|---|---|
| A template screen file ("do it like ProfileScreen.kt") | The project's state and navigation pattern gets reproduced automatically |
| Splitting stateful and stateless versions | Testing and previewing are only possible on the stateless version |
| Requiring `@Preview` | Fast visual verification and documentation |
| Strings must live in `strings.xml` | The agent tends to hardcode strings |
| The compile command | The agent can verify itself |
@Preview is especially valuable when working with an agent: previews get compiled, so syntax and type errors surface immediately, and you can eyeball the result in Android Studio. That makes the "a preview for every public Composable" rule more useful with an agent than without one.
📚 Sources and documentation
- Compose — state and state hoistingofficialdeveloper.android.com
- Compose performanceofficialdeveloper.android.com
Recomposition, stability and compiler metrics.
- Safely collecting flows in Composeofficialdeveloper.android.com
`collectAsStateWithLifecycle` and the lifecycle-aware APIs.