关于 compose-agent
compose-agent 是用于审查、编写和修改 Jetpack Compose 代码的技能,依据现代 AndroidX 实践检查实现。它覆盖状态、生命周期、性能、导航、测试和动画等方面。
主要功能
- 01检查弃用 API 与 AndroidX 指南
- 02审查状态、副作用和 Flow 收集
- 03评估性能与 Modifier 用法
- 04指导导航、动画和分页
- 05编写 Compose 代码时应用规则
适用场景
- 01检查界面的 Flow 收集是否遵循生命周期
- 02编写状态与 Modifier 用法恰当的 Composable
- 03检查分页列表的稳定键和加载状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hamen/ compose_skill --skill compose-agent安装后在 Claude Code 中输入/compose-agent确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.claude-plugin/plugin.json.cursor-plugin/plugin.jsonevals/evals.jsonreferences/animation.mdapi.mdcomponent-api.mdconcurrency.mdeffects.mdflows.mdfocus.mdkmp.mdkotlin.mdmodifiers.mdnavigation.mdpaging.mdperformance.mdstate.mdtesting.md详细介绍
Compose Agent
审查、编写或修改 Jetpack Compose 代码,确保代码正确、使用现代 API 并遵循 AndroidX 官方指南。只报告真实问题——不要吹毛求疵或凭空捏造问题。
目标技术路线(除非仓库另有说明):
- Kotlin
2.0.20+,搭配 Compose Compiler1.5.4+(默认启用 Strong Skipping Mode)。 - 使用最新的 Jetpack Compose BOM、Material 3,以及搭配
collectAsStateWithLifecycle()的androidx.lifecycle。 - 项目能够采用时使用 Navigation 3(
androidx.navigation3);否则使用 Navigation 2.8+。 - 使用 Coroutines + Flow 作为异步原语。不要使用 RxJava 或阻塞式 I/O。
如果仓库固定使用较旧版本,请与仓库保持一致,但需用一行说明现代方案的做法。
审查流程
- 使用
references/api.md检查已弃用或即将弃用的 API。 - 使用
references/state.md验证状态和数据流。 - 使用
references/effects.md验证副作用的选择(LaunchedEffect、DisposableEffect、produceState、snapshotFlow、rememberUpdatedState)。 - 使用
references/performance.md审查可组合项性能——稳定性、lambda 修饰符、惰性列表键、延迟读取、跨阶段反向写入。 - 使用
references/modifiers.md审查修饰符用法——顺序、lambda 形式,以及使用Modifier.Node而非composed { }。 - 使用
references/navigation.md审查导航——在修改调用点之前,先参考其中的决策表(Nav3、类型安全的 Nav2,还是普通状态)。 - 使用
references/concurrency.md审查协程和生命周期感知的收集。 - 使用
references/flows.md审查 Flow 运算符以及 StateFlow / SharedFlow 的设计(stateIn、shareIn、flatMap变体、combine、错误处理、背压、asStateFlow())。 - 使用
references/component-api.md审查可组合项 API 设计(参数顺序、插槽、命名、默认值)。 - 当测试、语义、截图、预览或 UI 伪依赖与审查相关时,使用
references/testing.md审查 UI 测试模式。 - 当 UI 使用焦点 API、键盘输入、TV、桌面、ChromeOS 或无障碍焦点行为时,使用
references/focus.md审查焦点和键盘 / D-pad 导航。 - 当审查涉及通用代码、
expect/actual、平台服务、原生视图或共享 UI 目标时,使用references/kmp.md审查 Compose Multiplatform / KMP 边界。 - 当审查涉及任何
animate*、Animatable、Transition、AnimatedVisibility、AnimatedContent或无限转场 API 时,使用references/animation.md审查动画 API 的选择和生命周期——声明式还是命令式、通过remember创建的Animatable、由目标驱动的启动、优先使用spring而非tween、延迟读取动画值、AnimatedContent的键。 - 当审查涉及
LazyPagingItems、PagingData或collectAsLazyPagingItems时,使用references/paging.md审查 Compose 中的 Paging 3——collectAsLazyPagingItems(Paging differ——不是collectAsStateWithLifecycle,后者不适用于PagingData)、稳定的itemKey、LoadState分支、由用户触发的refresh()/retry()。 - 最后使用
references/kotlin.md进行 Kotlin 风格检查。
如果只进行部分审查,只加载相关的参考文件——每个参考文件都设计为可独立阅读。
核心指示
- 在组合阶段保持可组合项无副作用。所有 I/O、对已记忆对象之外的状态进行的修改、分析统计等,都应放在
LaunchedEffect、DisposableEffect、produceState、ViewModel 中,或者仅对由事件/手势驱动的工作,在处理函数中使用rememberCoroutineScope().launch。由目标值驱动的动画应放在LaunchedEffect中,或使用声明式animate*AsState/updateTransition,而不是在组合函数体中通过scope.launch启动。参见references/animation.md和references/effects.md。 - 所有接受
Modifier的可组合项都必须将参数命名为modifier,类型声明为Modifier = Modifier,并且只应用于最外层布局。绝不要创建未向下传递的modifier参数。 - 组件的参数顺序:必需数据 →
modifier: Modifier = Modifier→ 其他带默认值的可选参数 → 最后是尾随content: @Composable () -> Unit插槽。每个组件只能有一个modifier参数。 - 对于有状态 API,提供无状态重载,以及用于提升
remember的有状态便捷 API。参见references/component-api.md。 - 在 UI 代码中使用
collectAsStateWithLifecycle()收集 Flow。普通的collectAsState()会在屏幕不可见时继续收集,消耗电量和带宽。**例外:**使用collectAsLazyPagingItems()收集Flow<PagingData<T>>,绝不要使用collectAsStateWithLifecycle()——参见references/paging.md。 - 对于应在配置更改或进程死亡后保留的 UI 状态,优先使用
rememberSaveable而非remember,除非该值无法序列化或可以推导得出。 - 对基本类型的状态使用类型化状态工厂(
mutableIntStateOf、mutableLongStateOf、mutableFloatStateOf、mutableDoubleStateOf)。直接使用mutableStateOf<Int>(...)会产生装箱。 - 绝不要写入某个阶段已读取的状态。布局回调(
onSizeChanged/onGloballyPositioned/onPlaced)不得写入由同级项在组合阶段读取的状态(跨阶段回写,会形成测量 → 重组循环);也绝不要在同时读取mutableStateListOf/mutableStateMapOf的可组合函数体中修改它们(组合阶段自失效,会形成修改 → 重组循环)。在布局/绘制阶段使用测量值,或从事件、LaunchedEffect/状态持有对象中修改快照状态。参见references/performance.md。 - 不要引入无效的“优化”:对纯函数使用
remember(index),或在 Strong Skipping 下对已自动记忆的回调再次使用remember(SSM 即使遇到不稳定捕获值,也会记忆 lambda)。应通过 Layout Inspector 计数或编译器报告证明重组确实有所减少,而不是仅凭代码中出现了某种模式。参见references/performance.md。 - 绝不要在
CompositionLocal中放置 lambda。请使用显式参数。 - 未经事先询问,不要引入第三方库。用于分页器、下拉刷新、流式布局和系统 UI 控制器的 Accompanist 库均已弃用——这些功能现在已包含在 AndroidX 中(
HorizontalPager、PullToRefreshBox、FlowRow/FlowColumn、enableEdgeToEdge())。 - 当元素的背景直接或通过混合读取
MaterialTheme.colorScheme.*时,其文本和图标颜色必须读取相同的主题来源。在由主题驱动的背景上使用硬编码的Color.Black/Color.White/原始 ARGB 字面值会导致深色模式问题。参见references/component-api.md。 - 优先使用声明式动画。单个由状态驱动的值使用
animate*AsState,同步的值使用updateTransition;只有需要命令式控制(手势、惯性滑动、序列动画)时才使用Animatable。始终使用remember记忆Animatable,并从LaunchedEffect启动由目标值驱动的动画(绝不要在组合阶段通过scope.launch响应状态变化);对于可中断的运动,优先使用spring()而非tween;在布局/绘制阶段读取动画值(使用 lambda 修饰符/graphicsLayer),以避免动画每帧都触发重组。参见references/animation.md。 - 对于分页信息流,使用
collectAsLazyPagingItems(),通过items(count = …, key = …)和稳定的领域 ID 进行渲染,根据loadState分别处理加载、空状态、错误和追加加载,并且只在用户操作时调用refresh()/retry()——不要在组合函数体中调用。参见references/paging.md。
输出格式(评审模式)
按文件整理问题。对于每个问题:
- 写明文件和相关行号。
- 指出违反的规则(例如,“使用
collectAsStateWithLifecycle(),而不是collectAsState()”)。 - 展示简短的修复前后代码。
- 链接官方来源(
developer.android.com/...或 AndroidX 组件指南)。
跳过没有问题的文件。最后以优先级顺序给出摘要,最多 三 项,影响最大的排在最前。
输出示例
feature/profile/ProfileScreen.kt
第 34 行:使用 collectAsStateWithLifecycle() —— 屏幕未处于 STARTED 状态时,UI Flow 必须停止收集。
// Before
val user by viewModel.user.collectAsState()
// After
val user by viewModel.user.collectAsStateWithLifecycle()
https://developer.android.com/topic/architecture/ui-layer/state-production#continuous-vs-discrete
第 58 行:将 modifier 传给最外层布局,而不是内部内容。
// Before
@Composable
fun UserCard(user: User, modifier: Modifier = Modifier) {
Column {
Text(user.name, modifier = modifier) // modifier swallowed here
Text(user.email)
}
}
// After
@Composable
fun UserCard(user: User, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(user.name)
Text(user.email)
}
}
https://developer.android.com/develop/ui/compose/api-guidelines#naming-modifiers
第 72 行:使用 lambda 形式,将动画 offset 的读取延后到布局阶段。
// Before
val dx by animateDpAsState(targetValue = if (expanded) 0.dp else (-200).dp, label = "drawerOffset")
Box(modifier = Modifier.offset(x = dx))
// After — layout-phase read, skips recomposition on every frame
val dx by animateDpAsState(targetValue = if (expanded) 0.dp else (-200).dp, label = "drawerOffset")
Box(modifier = Modifier.offset { IntOffset(dx.roundToPx(), 0) })
摘要
- 性能(高):
ProfileScreen.kt:72、HomeScreen.kt:110和DetailScreen.kt:88中存在未延后的动画读取,每帧都会触发重组。改用 lambda 形式的修饰符。 - 生命周期(高): 三个屏幕使用了
collectAsState()—— 替换为collectAsStateWithLifecycle(),以避免在后台收集。 - API 设计(中):
UserCard未传递其modifier参数。将它转发给最外层布局。
示例结束。
编写模式
当 Agent 编写新代码而非进行评审时,同样的规则适用,可作为防护准则。生成可组合项之前,请在内部检查:
- 是否包含
modifier: Modifier = Modifier? - 状态是否已提升,或者是否有明确理由在此处持有状态?
- 如果渲染列表,是否使用了稳定的
key =? - 如果启动任务,该任务是否放在
LaunchedEffect、produceState或 ViewModel 中,而不是组合函数体内? - 如果收集 Flow,是否使用了
collectAsStateWithLifecycle()?(Flow<PagingData<T>>是例外,应使用collectAsLazyPagingItems()。) - 参数顺序是否为:数据 → modifier → 其他参数 → 最后是内容槽?
- 如果使用动画,API 是否优先采用声明式方式、在需要时进行记忆化、具备生命周期感知且符合阶段要求?
- 是否有任何布局回调或快照收集中的状态变更,会写入在组合中读取的状态(从而形成跨阶段/自失效循环)?
- 如果使用分页,键是否稳定、是否处理了
LoadState,刷新/重试是否由用户触发? - 如果涉及焦点、测试或特定平台行为,是否已在作出判断前加载了相应的参考资料?
如果任一答案为否,且没有刻意为之的理由,请在返回代码前修正。
此技能不涵盖的内容
v1 中不在范围内的内容——请委派给其他技能,或明确缩小范围:
- Material 3 合规性、主题和设计令牌——使用
material-3技能。 - 使用数字评分为现有代码库打分——使用同一仓库中的同级
jetpack-compose-audit技能。 - Wear OS/TV/Auto/Glance 深度平台评审——此技能现已涵盖焦点和键盘/D-pad 基础知识,但不涵盖完整的平台认证。
- 无障碍深度评审——我们会指出明显的问题(缺少
contentDescription、没有标签的纯图标按钮、触控目标小于 48dp),但不会评分。
如果用户需要上述任一内容,请缩小范围并说明。
参考资料
references/api.md— 已弃用和软弃用的 Compose API 及其现代替代方案。references/state.md— 状态提升、remember与rememberSaveable的区别、ViewModel 边界、derivedStateOf。references/effects.md—LaunchedEffect、DisposableEffect、SideEffect、produceState、snapshotFlow、rememberUpdatedState。references/performance.md— 稳定性、Strong Skipping Mode、lambda 修饰符、惰性列表键、类型化状态工厂、延迟读取。references/modifiers.md— 修饰符顺序、lambda 形式、用Modifier.Node替代composed { }。references/navigation.md— 决策表(Nav3、类型安全的 Nav2 与普通状态)、Navigation 3,以及 Navigation 2 中应替换的内容。references/concurrency.md— 协程、Flow、collectAsStateWithLifecycle、repeatOnLifecycle、作用域选择。references/flows.md— 操作符选择:StateFlow与SharedFlow及冷Flow、stateIn(WhileSubscribed)、shareIn、flatMap变体、combine/merge/zip、错误处理、背压、asStateFlow()的暴露方式。references/component-api.md— 可组合 API 指南:参数顺序、插槽、命名、默认值、状态提升形式。references/testing.md— Compose UI 测试、语义断言、截图测试、图像和平台服务的伪实现、预览。references/focus.md—FocusRequester、焦点恢复、键盘 / D-pad 输入、按键处理器和焦点测试。references/kmp.md— Kotlin Multiplatform 和 Compose Multiplatform 边界、expect/actual、接口、平台叶级可组合项。references/animation.md— 动画 API 选择(animate*AsState、updateTransition与Animatable)、用remember记住的动画状态、目标驱动的启动、手势驱动的snapTo/animateDecay、spring/tween/keyframes、减少动态效果、延迟读取动画值、AnimatedContent/AnimatedVisibility、animateItem()、屏幕外的无限动画。references/paging.md— Compose 中的 Paging 3:何时使用分页而不是普通列表、collectAsLazyPagingItems、稳定的itemKey、LoadStateUI、下拉刷新、分页惰性列表的反模式。references/kotlin.md— LLM 经常遗漏的 Kotlin 编码约定和 Android Kotlin 风格。
验收评测
evals/evals.json 保存了写入模式的验收用例——要求此技能编写 Compose 代码的提示词,以及生成的代码必须满足的预期。用例 0–2 与 jetpack-compose-audit 的评分规则完全一致(跨阶段回写、Strong Skipping 的错误导向、快照自失效)——bin/ci 会确保它们保持同步,避免编写路径偏离审计路径;用例 3–4 则增加了基础的阶段正确读取和生命周期感知的 Flow 收集。加载此技能后,针对每条提示词运行一个模型,并检查每项预期是否满足。
主要来源
此技能中的每条规则都源自以下某个来源:
https://developer.android.com/develop/ui/compose及其子页面https://developer.android.com/kotlin/style-guidehttps://kotlinlang.org/docs/coding-conventions.htmlhttps://www.jetbrains.com/help/kotlin-multiplatform-dev/https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.mdhttps://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md
如果补充来源(博客文章、会议演讲)与这些来源不一致,应以主要来源为准。