mcpField
C
compose-agent

compose-agent

github.com/hamen开发375167 次安装15 周前更新
代码仓库

关于 compose-agent

compose-agent 是用于审查、编写和修改 Jetpack Compose 代码的技能,依据现代 AndroidX 实践检查实现。它覆盖状态、生命周期、性能、导航、测试和动画等方面。

主要功能

  1. 01检查弃用 API 与 AndroidX 指南
  2. 02审查状态、副作用和 Flow 收集
  3. 03评估性能与 Modifier 用法
  4. 04指导导航、动画和分页
  5. 05编写 Compose 代码时应用规则

适用场景

  1. 01检查界面的 Flow 收集是否遵循生命周期
  2. 02编写状态与 Modifier 用法恰当的 Composable
  3. 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.json
evals/
evals.json
references/
animation.md
api.md
component-api.md
concurrency.md
effects.md
flows.md
focus.md
kmp.md
kotlin.md
modifiers.md
navigation.md
paging.md
performance.md
state.md
testing.md

详细介绍

Compose Agent

审查、编写或修改 Jetpack Compose 代码,确保代码正确、使用现代 API 并遵循 AndroidX 官方指南。只报告真实问题——不要吹毛求疵或凭空捏造问题。

目标技术路线(除非仓库另有说明):

  • Kotlin 2.0.20+,搭配 Compose Compiler 1.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。

如果仓库固定使用较旧版本,请与仓库保持一致,但需用一行说明现代方案的做法。

审查流程

  1. 使用 references/api.md 检查已弃用或即将弃用的 API。
  2. 使用 references/state.md 验证状态和数据流。
  3. 使用 references/effects.md 验证副作用的选择(LaunchedEffect、DisposableEffect、produceState、snapshotFlow、rememberUpdatedState)。
  4. 使用 references/performance.md 审查可组合项性能——稳定性、lambda 修饰符、惰性列表键、延迟读取、跨阶段反向写入。
  5. 使用 references/modifiers.md 审查修饰符用法——顺序、lambda 形式,以及使用 Modifier.Node 而非 composed { }。
  6. 使用 references/navigation.md 审查导航——在修改调用点之前,先参考其中的决策表(Nav3、类型安全的 Nav2,还是普通状态)。
  7. 使用 references/concurrency.md 审查协程和生命周期感知的收集。
  8. 使用 references/flows.md 审查 Flow 运算符以及 StateFlow / SharedFlow 的设计(stateIn、shareIn、flatMap 变体、combine、错误处理、背压、asStateFlow())。
  9. 使用 references/component-api.md 审查可组合项 API 设计(参数顺序、插槽、命名、默认值)。
  10. 当测试、语义、截图、预览或 UI 伪依赖与审查相关时,使用 references/testing.md 审查 UI 测试模式。
  11. 当 UI 使用焦点 API、键盘输入、TV、桌面、ChromeOS 或无障碍焦点行为时,使用 references/focus.md 审查焦点和键盘 / D-pad 导航。
  12. 当审查涉及通用代码、expect / actual、平台服务、原生视图或共享 UI 目标时,使用 references/kmp.md 审查 Compose Multiplatform / KMP 边界。
  13. 当审查涉及任何 animate*、Animatable、Transition、AnimatedVisibility、AnimatedContent 或无限转场 API 时,使用 references/animation.md 审查动画 API 的选择和生命周期——声明式还是命令式、通过 remember 创建的 Animatable、由目标驱动的启动、优先使用 spring 而非 tween、延迟读取动画值、AnimatedContent 的键。
  14. 当审查涉及 LazyPagingItems、PagingData 或 collectAsLazyPagingItems 时,使用 references/paging.md 审查 Compose 中的 Paging 3——collectAsLazyPagingItems(Paging differ——不是 collectAsStateWithLifecycle,后者不适用于 PagingData)、稳定的 itemKey、LoadState 分支、由用户触发的 refresh() / retry()。
  15. 最后使用 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。

输出格式(评审模式)

按文件整理问题。对于每个问题:

  1. 写明文件和相关行号。
  2. 指出违反的规则(例如,“使用 collectAsStateWithLifecycle(),而不是 collectAsState()”)。
  3. 展示简短的修复前后代码。
  4. 链接官方来源(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) })

https://developer.android.com/develop/ui/compose/performance/bestpractices#defer-reads-as-long-as-possible

摘要
  1. 性能(高): ProfileScreen.kt:72、HomeScreen.kt:110 和 DetailScreen.kt:88 中存在未延后的动画读取,每帧都会触发重组。改用 lambda 形式的修饰符。
  2. 生命周期(高): 三个屏幕使用了 collectAsState() —— 替换为 collectAsStateWithLifecycle(),以避免在后台收集。
  3. 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、LoadState UI、下拉刷新、分页惰性列表的反模式。
  • 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-guide
  • https://kotlinlang.org/docs/coding-conventions.html
  • https://www.jetbrains.com/help/kotlin-multiplatform-dev/
  • https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md
  • https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md

如果补充来源(博客文章、会议演讲)与这些来源不一致,应以主要来源为准。

评论

支持 Markdown