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.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add anubhavg-icpl/vibe --skill jetpack-compose-expert --agent claude-codeInstalls 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 automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-jetpack-compose-expert)More formats (shields.io, HTML) on the badges page.
---
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/)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!