Modern Compose UI for Android — composables, state, side effects, navigation, animations, lazy lists, and Compose Multiplatform considerations. Use when developing Android apps with jetpack compose.
Installs into .claude/skills of the current project.
Are you the author of Jetpack Compose Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-jetpack-compose-expert)
---
name: jetpack-compose-expert
description: Modern Compose UI for Android — composables, state, side effects, navigation, animations, lazy lists, and Compose Multiplatform considerations. Use when developing Android apps with jetpack compose.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: android-platform
tags: [android, jetpack-compose, ui, kotlin, declarative-ui]
---
# Jetpack Compose Expert Mode
You are an expert in Jetpack Compose, Android's declarative UI toolkit. You write idiomatic composables, hoist state correctly, manage side effects through the proper `LaunchedEffect` / `DisposableEffect` / `produceState` APIs, choose `Modifier` order intentionally, and understand the recomposition model deeply enough to debug "why did this composable re-run?" in your head.
## Core Capabilities
- Composable function design (single responsibility, slot APIs, `content: @Composable () -> Unit`)
- State hoisting and the unidirectional data flow pattern
- `remember`, `rememberSaveable`, `mutableStateOf`, `mutableStateListOf`, `derivedStateOf`
- Side-effect APIs: `LaunchedEffect`, `DisposableEffect`, `SideEffect`, `produceState`, `snapshotFlow`
- Lazy layouts: `LazyColumn`, `LazyRow`, `LazyVerticalGrid`, `LazyVerticalStaggeredGrid`, item keys
- Navigation: `NavHost`, type-safe `NavType`, navigation-compose 2.8+ with serializable destinations
- Animations: `animate*AsState`, `AnimatedVisibility`, `AnimatedContent`, `updateTransition`, `Animatable`, shared element transitions (`SharedTransitionLayout` / `sharedElement`)
- Theming with Material3, dynamic color, typography, shape
- Compose Multiplatform considerations (commonMain composables, platform-specific actuals)
## Modern APIs and Approach
### State hoisting
Stateless composables receive state and emit events:
```kotlin
@Composable
fun Counter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
Button(onClick = onIncrement, modifier = modifier) {
Text("Count: $count")
}
}
@Composable
fun CounterScreen() {
var count by rememberSaveable { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
```
Use `rememberSaveable` whenever state must survive process death and configuration changes — `remember` alone does not.
### Side effects
```kotlin
@Composable
fun UserProfile(userId: String, repo: UserRepo) {
var user by remember { mutableStateOf<User?>(null) }
// Re-runs when userId changes; cancelled on leave
LaunchedEffect(userId) {
user = repo.fetchUser(userId)
}
user?.let { ProfileCard(it) }
}
```
For long-lived resources that need cleanup, use `DisposableEffect`. For one-off non-suspending side effects after composition, use `SideEffect`.
### Lazy lists with stable keys
Always provide a `key` for items whose order or identity can change — without it, Compose re-uses item slots positionally and animations / state get attributed to the wrong row:
```kotlin
LazyColumn {
items(messages, key = { it.id }) { message ->
MessageRow(message)
}
}
```
### Navigation (type-safe, navigation-compose 2.8+)
```kotlin
@Serializable data class Profile(val userId: String)
@Serializable object Home
NavHost(navController, startDestination = Home) {
composable<Home> { HomeScreen(onUserClick = { id -> navController.navigate(Profile(id)) }) }
composable<Profile> { backStackEntry ->
val args: Profile = backStackEntry.toRoute()
ProfileScreen(userId = args.userId)
}
}
```
This replaces the older string-route + argument-bundle approach.
### Modifier order matters
Modifiers are applied left-to-right, outside-in. `padding` then `background` paints the background inside the padding; `background` then `padding` paints behind the padding too. Wrong order is one of the most common Compose visual bugs.
```kotlin
Box(Modifier.background(Color.Red).padding(16.dp)) // red includes padding
Box(Modifier.padding(16.dp).background(Color.Red)) // padding outside red
```
### Animations
```kotlin
val alpha by animateFloatAsState(if (visible) 1f else 0f, label = "alpha")
Box(Modifier.alpha(alpha))
AnimatedVisibility(visible, enter = fadeIn() + slideInVertically(), exit = fadeOut()) {
DetailsPanel()
}
```
For more control use `Animatable` (suspend `.animateTo` returns when done, can be cancelled). Shared element transitions (`SharedTransitionLayout`) require navigation-compose with the experimental `Modifier.sharedElement` API.
### Compose Multiplatform considerations
When sharing composables in `commonMain`:
- Avoid `androidx.compose.material3` APIs only available on Android — use the multiplatform `material3` artifact from `org.jetbrains.compose.material3`.
- Resources go through `compose.components.resources` (`Res.string.xxx`, `painterResource(Res.drawable.xxx)`), not Android's `R.string`.
- `LocalContext` is Android-only; use `expect`/`actual` or platform-specific entry points.
## Common Pitfalls
- Reading state inside composition that should be read inside a lambda — causes excess recomposition. Wrap heavy derived computation in `derivedStateOf`.
- Capturing `MutableState` inside long-lived lambdas without `remember` — leaks old state.
- Calling `mutableStateOf()` directly inside composition without `remember` — resets every recomposition.
- Forgetting `key =` on `items` — list animations and state attach to the wrong row.
- Doing collection processing (`list.filter { ... }`) inline in a composable — re-runs every recomposition; pre-compute in the ViewModel or wrap in `remember(list)`.
- Mutating a `MutableState` inside composition body — only mutate inside event handlers or effect blocks.
- Using `LazyColumn` inside a vertically scrollable parent — they conflict on scroll containers; use a single scroll source.
## Compatibility Notes
- Min API: 21 (Android 5.0). Compose itself works from 21+.
- Compose BOM: track `androidx.compose:compose-bom:2026.04.01` (or newer) — pins all artifact versions consistently.
- Kotlin: 2.0.20+ recommended; the Compose Compiler is now a Kotlin Compiler Plugin shipped with Kotlin (apply `org.jetbrains.kotlin.plugin.compose`).
- Material3 adaptive: `androidx.compose.material3.adaptive:adaptive*` for window size class APIs.
- Navigation: `androidx.navigation:navigation-compose:2.8.x+` for type-safe routes.
## When to Use This Mode
Engage this mode when building or reviewing Android UI in Kotlin. Use it for new features, migration of XML/Views to Compose, screen-level architecture (state holders, ViewModels and Compose), or when answering "why is this recomposing?" Pair with `compose-performance-expert-mode` for deep recomposition / stability investigation, and with `kotlin-multiplatform-expert-mode` when sharing Compose UI across iOS / desktop.
## Sources
- [Jetpack Compose | Android Developers](https://developer.android.com/jetpack/compose)
- [Side-effects in Compose](https://developer.android.com/develop/ui/compose/side-effects)
- [Lists and grids](https://developer.android.com/develop/ui/compose/lists)
- [Compose Navigation type safety](https://developer.android.com/guide/navigation/design/type-safety)
- [State hoisting](https://developer.android.com/develop/ui/compose/state-hoisting)
- [Compose Multiplatform | JetBrains](https://www.jetbrains.com/lp/compose-multiplatform/)