| name | compose-state-holder-ui-split |
| description | Use when a Jetpack Compose screen-level composable takes a ViewModel/component/controller, collects state or effects, handles navigation/snackbars, or wires callbacks while also rendering layout. Technique-layer skill — complements the codebase-specific compose-expert and feed-patterns. |
Compose: state holder/UI split
Core principle
Separate state-holder wiring from UI rendering. The state-holder composable talks to ViewModels, components, flows, navigation, and side effects. The UI composable takes plain immutable UI state plus callbacks and describes layout.
This keeps screens previewable, testable, and easier to reuse across Android, Desktop, TV, and KMP/CMP targets.
When to use this skill
Use this when a Compose screen:
- Takes a ViewModel, component, controller, navigator, repository, or service directly.
- Collects app/business state or side effects in the same function that lays out most UI.
- Passes a whole state holder into child composables instead of explicit state and callbacks.
- Is hard to preview because it needs dependency injection, navigation, lifecycle, or fake services.
- Has UI tests that must construct a full app stack to verify a simple layout branch.
The pattern
Use a small public state-holder composable:
@Composable
fun ProfileScreen(component: ProfileComponent, modifier: Modifier = Modifier) {
val state by component.state.collectAsStateWithLifecycle()
ProfileScreen(
state = state,
onNameChange = component::onNameChange,
onSaveClick = component::save,
onBackClick = component::back,
modifier = modifier,
)
}