compose-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Compose Multiplatform Expert

Compose Multiplatform 实战指南

Visual UI patterns for sharing composables across Android and Desktop.
适用于在Android和Desktop之间共享可组合项的可视化UI模式。

When to Use This Skill

何时使用本技能

  • Creating or refactoring shared UI components
  • Deciding whether to share UI in
    commonMain
    or keep platform-specific
  • Building custom ImageVector icons (robohash pattern)
  • State management: remember, derivedStateOf, produceState
  • Recomposition optimization: visual usage of @Stable/@Immutable
  • Material3 theming and styling
  • Performance: lazy lists, image loading
Delegate to other skills:
  • Navigation structure →
    android-expert
    ,
    desktop-expert
  • Kotlin state patterns (StateFlow, sealed classes) →
    kotlin-expert
  • Build configuration →
    gradle-expert
  • 创建或重构共享UI组件
  • 决定是在
    commonMain
    中共享UI还是保留平台特定实现
  • 构建自定义ImageVector图标(robohash模式)
  • 状态管理:remember、derivedStateOf、produceState
  • 重组优化:@Stable/@Immutable的可视化用法
  • Material3主题与样式设计
  • 性能优化:懒加载列表、图片加载
委托给其他技能:
  • 导航结构 →
    android-expert
    desktop-expert
  • Kotlin状态模式(StateFlow、密封类) →
    kotlin-expert
  • 构建配置 →
    gradle-expert

Philosophy: Share by Default

设计理念:默认共享

Default to
commons/commonMain
unless platform experts indicate otherwise.
默认优先使用
commons/commonMain
,除非平台专家另有指示。

Always Share

始终共享

  • UI components: Buttons, cards, lists, dialogs, inputs
  • State visualization: Loading, empty, error states
  • Custom icons: ImageVector assets (robohash, custom paths)
  • Theme utilities: Color calculations, style helpers
  • Material3 components: Any UI using Material primitives
  • UI组件:按钮、卡片、列表、对话框、输入框
  • 状态可视化:加载、空数据、错误状态
  • 自定义图标:ImageVector资源(robohash、自定义路径)
  • 主题工具:颜色计算、样式辅助工具
  • Material3组件:所有使用Material原语的UI

Keep Platform-Specific

保留平台特定实现

  • Navigation structure: Bottom nav (Android) vs Sidebar (Desktop)
  • Screen layouts: Platform-specific scaffolding
  • System integrations: File pickers, notifications, share sheets
  • Platform UX: Gestures, keyboard shortcuts, window management
  • 导航结构:底部导航(Android) vs 侧边栏(Desktop)
  • 屏幕布局:平台特定的脚手架
  • 系统集成:文件选择器、通知、分享面板
  • 平台UX:手势、键盘快捷键、窗口管理

Decision Framework

决策框架

  1. Uses only Material3 primitives? → Share in
    commonMain
  2. Requires platform system APIs? → Platform-specific
  3. Pure visual component without navigation? → Share in
    commonMain
  4. Needs platform UX patterns? → Ask
    android-expert
    or
    desktop-expert
If uncertain, default to sharing - easier to split later than merge.
  1. 是否仅使用Material3原语? → 在
    commonMain
    中共享
  2. 是否需要平台系统API? → 保留平台特定实现
  3. 是否为无导航的纯可视化组件? → 在
    commonMain
    中共享
  4. 是否需要平台UX模式? → 咨询
    android-expert
    desktop-expert
若不确定,默认选择共享——后续拆分比合并更容易。

Shared Composable Anatomy

共享可组合项结构

Structure

结构示例

kotlin
@Composable
fun SharedComponent(
    // State parameters (read-only)
    data: DataClass,
    isLoading: Boolean,
    // Event parameters (write-only)
    onAction: () -> Unit,
    // Visual parameters
    modifier: Modifier = Modifier,
    // Optional customization
    colors: ComponentColors = ComponentDefaults.colors()
) {
    // Implementation
}
Pattern: State down, events up
  • Parameters above modifier = required state/events
  • modifier
    parameter = layout control
  • Parameters below modifier = optional customization
kotlin
@Composable
fun SharedComponent(
    // 状态参数(只读)
    data: DataClass,
    isLoading: Boolean,
    // 事件参数(只写)
    onAction: () -> Unit,
    // 可视化参数
    modifier: Modifier = Modifier,
    // 可选自定义配置
    colors: ComponentColors = ComponentDefaults.colors()
) {
    // 实现代码
}
模式:状态向下传递,事件向上传递
  • modifier之前的参数:必填状态/事件
  • modifier
    参数:布局控制
  • modifier之后的参数:可选自定义配置

Example: AddButton

示例:AddButton

kotlin
@Composable
fun AddButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    text: String = "Add",
    enabled: Boolean = true
) {
    OutlinedButton(
        modifier = modifier,
        enabled = enabled,
        onClick = onClick,
        shape = ActionButtonShape,
        contentPadding = ActionButtonPadding
    ) {
        Text(text = text, textAlign = TextAlign.Center)
    }
}

// Shared constants for consistency
val ActionButtonShape = RoundedCornerShape(20.dp)
val ActionButtonPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp)
Why this works on all platforms:
  • Material3 primitives (OutlinedButton, Text)
  • No platform APIs
  • Configurable through parameters
  • Consistent styling via shared constants
kotlin
@Composable
fun AddButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    text: String = "Add",
    enabled: Boolean = true
) {
    OutlinedButton(
        modifier = modifier,
        enabled = enabled,
        onClick = onClick,
        shape = ActionButtonShape,
        contentPadding = ActionButtonPadding
    ) {
        Text(text = text, textAlign = TextAlign.Center)
    }
}

// 用于保持一致性的共享常量
val ActionButtonShape = RoundedCornerShape(20.dp)
val ActionButtonPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp)
为何能在全平台生效:
  • 使用Material3原语(OutlinedButton、Text)
  • 未调用平台API
  • 通过参数实现可配置
  • 通过共享常量保持样式一致

State Management Patterns

状态管理模式

remember - Cache Across Recompositions

remember - 在重组间缓存状态

kotlin
@Composable
fun ExpandableCard() {
    var isExpanded by remember { mutableStateOf(false) }

    Column {
        IconButton(onClick = { isExpanded = !isExpanded }) {
            Icon(
                if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
                contentDescription = if (isExpanded) "Collapse" else "Expand"
            )
        }

        if (isExpanded) {
            Text("Expanded content...")
        }
    }
}
Visual pattern: Toggle button → state changes → UI expands/collapses Use for: Simple UI state (toggles, counters, text input)
kotlin
@Composable
fun ExpandableCard() {
    var isExpanded by remember { mutableStateOf(false) }

    Column {
        IconButton(onClick = { isExpanded = !isExpanded }) {
            Icon(
                if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
                contentDescription = if (isExpanded) "Collapse" else "Expand"
            )
        }

        if (isExpanded) {
            Text("Expanded content...")
        }
    }
}
可视化模式:切换按钮 → 状态变更 → UI展开/收起 适用场景:简单UI状态(开关、计数器、文本输入)

derivedStateOf - Optimize Frequent Changes

derivedStateOf - 优化频繁变更

kotlin
@Composable
fun ScrollToTopButton(listState: LazyListState) {
    // Only recomposes when showButton changes, not every scroll pixel
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 0
        }
    }

    if (showButton) {
        FloatingActionButton(onClick = { /* scroll to top */ }) {
            Icon(Icons.Default.ArrowUpward, null)
        }
    }
}
Visual pattern: Scroll position (0, 1, 2...) → boolean (show/hide) → Button visibility Use for: Input changes frequently, derived result changes rarely Performance: Prevents recomposition on every scroll event
kotlin
@Composable
fun ScrollToTopButton(listState: LazyListState) {
    // 仅在showButton变化时重组,而非每次滚动像素变化
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 0
        }
    }

    if (showButton) {
        FloatingActionButton(onClick = { /* scroll to top */ }) {
            Icon(Icons.Default.ArrowUpward, null)
        }
    }
}
可视化模式:滚动位置(0、1、2...) → 布尔值(显示/隐藏) → 按钮可见性 适用场景:输入频繁变化,但派生结果很少变化的情况 性能优势:避免每次滚动事件都触发重组

produceState - Async to Compose State

produceState - 异步转Compose状态

kotlin
@Composable
fun LoadUserProfile(userId: String): State<User?> {
    return produceState<User?>(initialValue = null, userId) {
        value = repository.fetchUser(userId)
    }
}

@Composable
fun ProfileScreen(userId: String) {
    val user by LoadUserProfile(userId)

    when (user) {
        null -> LoadingState("Loading profile...")
        else -> ProfileCard(user!!)
    }
}
Visual pattern: Async operation → state updates → UI reflects changes Use for: Convert Flow, LiveData, callbacks into Compose state Lifecycle: Coroutine cancelled when composable leaves composition
For Kotlin-specific state patterns (StateFlow, sealed classes), see
kotlin-expert
.
kotlin
@Composable
fun LoadUserProfile(userId: String): State<User?> {
    return produceState<User?>(initialValue = null, userId) {
        value = repository.fetchUser(userId)
    }
}

@Composable
fun ProfileScreen(userId: String) {
    val user by LoadUserProfile(userId)

    when (user) {
        null -> LoadingState("Loading profile...")
        else -> ProfileCard(user!!)
    }
}
可视化模式:异步操作 → 状态更新 → UI反映变更 适用场景:将Flow、LiveData、回调转换为Compose状态 生命周期:当可组合项离开组合时,协程会被取消
关于Kotlin特定的状态模式(StateFlow、密封类),请参考
kotlin-expert

State Hoisting

状态提升

Move state up to make composables reusable:
kotlin
// ❌ Stateful - hard to test, can't control externally
@Composable
fun BadSearchBar() {
    var query by remember { mutableStateOf("") }
    TextField(value = query, onValueChange = { query = it })
}

// ✅ Stateless - reusable, testable
@Composable
fun GoodSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    TextField(
        value = query,
        onValueChange = onQueryChange,
        modifier = modifier
    )
}

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }

    Column {
        GoodSearchBar(query = query, onQueryChange = { query = it })
        SearchResults(query = query)
    }
}
Principle: State up, events down
  • State:
    query: String
    (read-only parameter)
  • Events:
    onQueryChange: (String) -> Unit
    (callback parameter)
将状态上移,使可组合项更具复用性:
kotlin
// ❌ 有状态 - 难以测试,无法外部控制
@Composable
fun BadSearchBar() {
    var query by remember { mutableStateOf("") }
    TextField(value = query, onValueChange = { query = it })
}

// ✅ 无状态 - 可复用、可测试
@Composable
fun GoodSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    TextField(
        value = query,
        onValueChange = onQueryChange,
        modifier = modifier
    )
}

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }

    Column {
        GoodSearchBar(query = query, onQueryChange = { query = it })
        SearchResults(query = query)
    }
}
原则:状态向上传递,事件向上传递
  • 状态:
    query: String
    (只读参数)
  • 事件:
    onQueryChange: (String) -> Unit
    (回调参数)

Recomposition Optimization

重组优化

Visual Usage of @Immutable

@Immutable的可视化用法

Use @Immutable on data classes passed to composables:
kotlin
@Immutable
data class UserProfile(val name: String, val avatar: String)

@Composable
fun ProfileCard(profile: UserProfile) {
    // Only recomposes when profile instance changes
    Row {
        RobohashImage(robot = profile.avatar)
        Text(profile.name, style = MaterialTheme.typography.titleMedium)
    }
}
Visual effect: Prevents recomposition when parent recomposes with same data Pattern: Mark parameter data classes as @Immutable Note: For Kotlin language details on @Immutable, see
kotlin-expert
在传递给可组合项的数据类上使用@Immutable:
kotlin
@Immutable
data class UserProfile(val name: String, val avatar: String)

@Composable
fun ProfileCard(profile: UserProfile) {
    // 仅当profile实例变化时才会重组
    Row {
        RobohashImage(robot = profile.avatar)
        Text(profile.name, style = MaterialTheme.typography.titleMedium)
    }
}
可视化效果:当父组件使用相同数据重组时,避免当前组件重组 模式:将参数数据类标记为@Immutable 注意:关于@Immutable的Kotlin语言细节,请参考
kotlin-expert

Stable Parameters

稳定参数

kotlin
// ✅ Stable - won't trigger recomposition unless colors instance changes
@Composable
fun ThemedCard(
    content: String,
    colors: CardColors = CardDefaults.colors(),
    modifier: Modifier = Modifier
) {
    Card(colors = colors, modifier = modifier) {
        Text(content)
    }
}
For @Stable annotation details, see
kotlin-expert
.
kotlin
// ✅ 稳定 - 除非colors实例变化,否则不会触发重组
@Composable
fun ThemedCard(
    content: String,
    colors: CardColors = CardDefaults.colors(),
    modifier: Modifier = Modifier
) {
    Card(colors = colors, modifier = modifier) {
        Text(content)
    }
}
关于@Stable注解的细节,请参考
kotlin-expert

Material3 Theming

Material3 主题

All shared composables use Material3 for consistency:
kotlin
@Composable
fun ThemedComponent() {
    val bg = MaterialTheme.colorScheme.background
    val fg = MaterialTheme.colorScheme.onBackground
    val primary = MaterialTheme.colorScheme.primary

    Column(
        modifier = Modifier.background(bg)
    ) {
        Text(
            "Title",
            style = MaterialTheme.typography.headlineMedium,
            color = fg
        )
        Button(
            onClick = { /* ... */ },
            colors = ButtonDefaults.buttonColors(containerColor = primary)
        ) {
            Text("Action")
        }
    }
}
Principles:
  • Colors:
    MaterialTheme.colorScheme.*
  • Typography:
    MaterialTheme.typography.*
  • Shapes:
    MaterialTheme.shapes.*
所有共享可组合项均使用Material3以保持一致性:
kotlin
@Composable
fun ThemedComponent() {
    val bg = MaterialTheme.colorScheme.background
    val fg = MaterialTheme.colorScheme.onBackground
    val primary = MaterialTheme.colorScheme.primary

    Column(
        modifier = Modifier.background(bg)
    ) {
        Text(
            "Title",
            style = MaterialTheme.typography.headlineMedium,
            color = fg
        )
        Button(
            onClick = { /* ... */ },
            colors = ButtonDefaults.buttonColors(containerColor = primary)
        ) {
            Text("Action")
        }
    }
}
设计原则:
  • 颜色:
    MaterialTheme.colorScheme.*
  • 排版:
    MaterialTheme.typography.*
  • 形状:
    MaterialTheme.shapes.*

Theme Detection

主题检测

kotlin
@Composable
private fun isLightTheme(): Boolean {
    val background = MaterialTheme.colorScheme.background
    return (background.red + background.green + background.blue) / 3 > 0.5f
}

@Composable
fun ThemedIcon() {
    val isDark = !isLightTheme()
    val tint = if (isDark) Color.White else Color.Black
    Icon(Icons.Default.Face, null, tint = tint)
}
kotlin
@Composable
private fun isLightTheme(): Boolean {
    val background = MaterialTheme.colorScheme.background
    return (background.red + background.green + background.blue) / 3 > 0.5f
}

@Composable
fun ThemedIcon() {
    val isDark = !isLightTheme()
    val tint = if (isDark) Color.White else Color.Black
    Icon(Icons.Default.Face, null, tint = tint)
}

Custom Icons: ImageVector Pattern

自定义图标:ImageVector模式

Amethyst uses ImageVector for multiplatform icons.
Amethyst使用ImageVector实现多平台图标。

roboBuilder DSL

roboBuilder DSL

kotlin
fun roboBuilder(block: Builder.() -> Unit): ImageVector {
    return ImageVector.Builder(
        name = "Robohash",
        defaultWidth = 300.dp,
        defaultHeight = 300.dp,
        viewportWidth = 300f,
        viewportHeight = 300f
    ).apply(block).build()
}
kotlin
fun roboBuilder(block: Builder.() -> Unit): ImageVector {
    return ImageVector.Builder(
        name = "Robohash",
        defaultWidth = 300.dp,
        defaultHeight = 300.dp,
        viewportWidth = 300f,
        viewportHeight = 300f
    ).apply(block).build()
}

Building Icons

构建图标

kotlin
fun customIcon(fgColor: SolidColor, builder: Builder) {
    builder.addPath(pathData1, fill = fgColor, stroke = Black, strokeLineWidth = 1.5f)
    builder.addPath(pathData2, fill = Black, fillAlpha = 0.4f)
    builder.addPath(pathData3, fill = Black, fillAlpha = 0.2f)
}

private val pathData1 = PathData {
    moveTo(144.5f, 87.5f)
    reflectiveCurveToRelative(-51.0f, 3.0f, -53.0f, 55.0f)
    lineToRelative(16.0f, 16.0f)
    close()
}

@Composable
fun CustomIcon() {
    Image(
        painter = rememberVectorPainter(
            roboBuilder {
                customIcon(SolidColor(Color.Blue), this)
            }
        ),
        contentDescription = "Custom icon"
    )
}
Why ImageVector?
  • Pure Kotlin, no XML
  • Works on Android, Desktop, iOS
  • GPU-accelerated
  • Type-safe
kotlin
fun customIcon(fgColor: SolidColor, builder: Builder) {
    builder.addPath(pathData1, fill = fgColor, stroke = Black, strokeLineWidth = 1.5f)
    builder.addPath(pathData2, fill = Black, fillAlpha = 0.4f)
    builder.addPath(pathData3, fill = Black, fillAlpha = 0.2f)
}

private val pathData1 = PathData {
    moveTo(144.5f, 87.5f)
    reflectiveCurveToRelative(-51.0f, 3.0f, -53.0f, 55.0f)
    lineToRelative(16.0f, 16.0f)
    close()
}

@Composable
fun CustomIcon() {
    Image(
        painter = rememberVectorPainter(
            roboBuilder {
                customIcon(SolidColor(Color.Blue), this)
            }
        ),
        contentDescription = "Custom icon"
    )
}
为何选择ImageVector?
  • 纯Kotlin实现,无需XML
  • 支持Android、Desktop、iOS
  • GPU加速
  • 类型安全

Caching Pattern

缓存模式

kotlin
object CustomIcons {
    private val cache = mutableMapOf<String, ImageVector>()

    fun get(key: String): ImageVector {
        return cache.getOrPut(key) {
            buildIcon(key)
        }
    }
}

@Composable
fun CachedIcon(key: String) {
    Image(imageVector = CustomIcons.get(key), contentDescription = null)
}
For detailed icon patterns, see
references/icon-assets.md
.
kotlin
object CustomIcons {
    private val cache = mutableMapOf<String, ImageVector>()

    fun get(key: String): ImageVector {
        return cache.getOrPut(key) {
            buildIcon(key)
        }
    }
}

@Composable
fun CachedIcon(key: String) {
    Image(imageVector = CustomIcons.get(key), contentDescription = null)
}
关于详细的图标模式,请参考
references/icon-assets.md

Common Visual Patterns

常见可视化模式

State Visualization

状态可视化

kotlin
@Composable
fun DataScreen(uiState: UiState) {
    when (uiState) {
        is UiState.Loading -> LoadingState("Loading...")
        is UiState.Empty -> EmptyState(
            title = "No data",
            onRefresh = { /* refresh */ }
        )
        is UiState.Error -> ErrorState(
            message = uiState.message,
            onRetry = { /* retry */ }
        )
        is UiState.Success -> ContentList(uiState.items)
    }
}
Components (all in
commons/commonMain
):
  • LoadingState
    - Progress indicator + message
  • EmptyState
    - Empty message + optional refresh button
  • ErrorState
    - Error message + optional retry button
kotlin
@Composable
fun DataScreen(uiState: UiState) {
    when (uiState) {
        is UiState.Loading -> LoadingState("Loading...")
        is UiState.Empty -> EmptyState(
            title = "No data",
            onRefresh = { /* refresh */ }
        )
        is UiState.Error -> ErrorState(
            message = uiState.message,
            onRetry = { /* retry */ }
        )
        is UiState.Success -> ContentList(uiState.items)
    }
}
组件(均位于
commons/commonMain
):
  • LoadingState
    - 进度指示器 + 提示消息
  • EmptyState
    - 空数据提示 + 可选刷新按钮
  • ErrorState
    - 错误提示 + 可选重试按钮

Relay Status (Amethyst Pattern)

中继状态(Amethyst模式)

kotlin
@Composable
fun RelayStatusIndicator(connectedCount: Int) {
    val statusColor = when {
        connectedCount == 0 -> RelayStatusColors.Disconnected
        connectedCount < 3 -> RelayStatusColors.Connecting
        else -> RelayStatusColors.Connected
    }

    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        Icon(
            imageVector = if (connectedCount > 0) Icons.Default.Check else Icons.Default.Close,
            tint = statusColor,
            modifier = Modifier.size(16.dp)
        )
        Text(
            "$connectedCount relay${if (connectedCount != 1) "s" else ""}",
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}
Visual mapping:
  • 0 relays → Red + X icon
  • 1-2 relays → Yellow + Check icon
  • 3+ relays → Green + Check icon
kotlin
@Composable
fun RelayStatusIndicator(connectedCount: Int) {
    val statusColor = when {
        connectedCount == 0 -> RelayStatusColors.Disconnected
        connectedCount < 3 -> RelayStatusColors.Connecting
        else -> RelayStatusColors.Connected
    }

    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        Icon(
            imageVector = if (connectedCount > 0) Icons.Default.Check else Icons.Default.Close,
            tint = statusColor,
            modifier = Modifier.size(16.dp)
        )
        Text(
            "$connectedCount relay${if (connectedCount != 1) "s" else ""}",
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
    }
}
视觉映射
  • 0个中继 → 红色 + X图标
  • 1-2个中继 → 黄色 + 对勾图标
  • 3个及以上中继 → 绿色 + 对勾图标

Placeholder Pattern

占位符模式

kotlin
@Composable
fun PlaceholderScreen(
    title: String,
    description: String,
    modifier: Modifier = Modifier
) {
    Column(modifier = modifier) {
        Text(title, style = MaterialTheme.typography.headlineMedium)
        Spacer(Modifier.height(16.dp))
        Text(description, color = MaterialTheme.colorScheme.onSurfaceVariant)
    }
}

// Specific implementations
@Composable
fun SearchPlaceholder() = PlaceholderScreen(
    title = "Search",
    description = "Search for users, notes, and hashtags."
)
Pattern: Generic composable + specific wrappers with preset text
kotlin
@Composable
fun PlaceholderScreen(
    title: String,
    description: String,
    modifier: Modifier = Modifier
) {
    Column(modifier = modifier) {
        Text(title, style = MaterialTheme.typography.headlineMedium)
        Spacer(Modifier.height(16.dp))
        Text(description, color = MaterialTheme.colorScheme.onSurfaceVariant)
    }
}

// 特定实现
@Composable
fun SearchPlaceholder() = PlaceholderScreen(
    title = "Search",
    description = "Search for users, notes, and hashtags."
)
模式:通用可组合项 + 预设文本的特定封装

Performance

性能优化

Avoid Unnecessary Recomposition

避免不必要的重组

kotlin
// ❌ Bad - recomposes on every scroll
@Composable
fun BadButton(scrollState: ScrollState) {
    if (scrollState.value > 100) {
        Button(onClick = {}) { Text("Top") }
    }
}

// ✅ Good - only recomposes when visibility changes
@Composable
fun GoodButton(scrollState: ScrollState) {
    val show by remember { derivedStateOf { scrollState.value > 100 } }
    if (show) {
        Button(onClick = {}) { Text("Top") }
    }
}
kotlin
// ❌ 不良实现 - 每次滚动都会重组
@Composable
fun BadButton(scrollState: ScrollState) {
    if (scrollState.value > 100) {
        Button(onClick = {}) { Text("Top") }
    }
}

// ✅ 良好实现 - 仅在可见性变化时重组
@Composable
fun GoodButton(scrollState: ScrollState) {
    val show by remember { derivedStateOf { scrollState.value > 100 } }
    if (show) {
        Button(onClick = {}) { Text("Top") }
    }
}

Lazy Lists

懒加载列表

kotlin
@Composable
fun FeedList(items: List<Item>) {
    LazyColumn {
        items(items, key = { it.id }) { item ->
            FeedItem(item)
        }
    }
}
Key principle: Use
key
parameter for stable item identity
kotlin
@Composable
fun FeedList(items: List<Item>) {
    LazyColumn {
        items(items, key = { it.id }) { item ->
            FeedItem(item)
        }
    }
}
核心原则:使用
key
参数确保列表项的稳定标识

Bundled Resources

捆绑资源

  • references/shared-composables-catalog.md - Complete catalog of shared UI components
  • references/state-patterns.md - State management patterns with visual examples
  • references/icon-assets.md - Custom ImageVector icon patterns
  • scripts/find-composables.sh - Find all @Composable functions in codebase
  • references/shared-composables-catalog.md - 完整的共享UI组件目录
  • references/state-patterns.md - 带可视化示例的状态管理模式
  • references/icon-assets.md - 自定义ImageVector图标模式
  • scripts/find-composables.sh - 在代码库中查找所有@Composable函数的脚本

Quick Reference

快速参考

TaskPatternLocation
Reusable UIState hoistingcommons/commonMain
Simple stateremember { mutableStateOf() }Composable scope
Derived statederivedStateOf { }remember block
Async → stateproduceState { }Composable function
Custom iconsroboBuilder + PathDatacommons/icons
Loading/ErrorLoadingState, ErrorStatecommons/ui/components
Theme colorsMaterialTheme.colorSchemeAny @Composable
NavigationDelegate to platform expertamethyst/, desktopApp/
任务模式位置
可复用UI状态提升commons/commonMain
简单状态remember { mutableStateOf() }可组合项作用域
派生状态derivedStateOf { }remember代码块
异步转状态produceState { }可组合项函数
自定义图标roboBuilder + PathDatacommons/icons
加载/错误状态LoadingState、ErrorStatecommons/ui/components
主题颜色MaterialTheme.colorScheme任意@Composable
导航委托给平台专家amethyst/、desktopApp/

Common Workflows

常见工作流

Creating a Shared Component

创建共享组件

  1. Start in
    commons/src/commonMain/kotlin/.../ui/components/
  2. Use Material3 primitives only
  3. Hoist state (parameters for data, callbacks for events)
  4. Add modifier parameter
  5. Use MaterialTheme for colors/typography
  6. Test on both Android and Desktop
  1. commons/src/commonMain/kotlin/.../ui/components/
    开始
  2. 仅使用Material3原语
  3. 提升状态(数据使用参数,事件使用回调)
  4. 添加modifier参数
  5. 使用MaterialTheme获取颜色/排版
  6. 在Android和Desktop上进行测试

Converting Existing Component

转换现有组件

  1. Read current implementation in
    amethyst/
    or
    desktopApp/
  2. Identify pure visual logic (no platform APIs)
  3. Create in
    commons/commonMain
    with hoisted state
  4. Replace platform implementations with shared component
  5. Keep platform-specific wrappers if needed
  1. 阅读
    amethyst/
    desktopApp/
    中的当前实现
  2. 识别纯可视化逻辑(无平台API)
  3. commons/commonMain
    中创建带状态提升的版本
  4. 使用共享组件替换平台特定实现
  5. 若需要,保留平台特定的封装

Custom Icon

创建自定义图标

  1. Export SVG from design tool
  2. Convert to PathData using Android Studio
  3. Create icon function with roboBuilder
  4. Add caching if generated dynamically
  5. Wrap in @Composable for easy use
  1. 从设计工具导出SVG
  2. 使用Android Studio转换为PathData
  3. 使用roboBuilder创建图标函数
  4. 若为动态生成的图标,添加缓存
  5. 封装为@Composable以便使用

Navigation (Delegate)

导航(委托)

For navigation patterns:
  • Android bottom nav →
    android-expert
  • Desktop sidebar →
    desktop-expert
  • Multi-window →
    desktop-expert
关于导航模式:
  • Android底部导航 →
    android-expert
  • Desktop侧边栏 →
    desktop-expert
  • 多窗口 →
    desktop-expert

Related Skills

相关技能

  • kotlin-expert - Kotlin language aspects (@Immutable details, StateFlow, sealed classes)
  • android-expert - Android navigation, platform APIs
  • desktop-expert - Desktop navigation, window management, OS specifics
  • kotlin-coroutines - Async patterns, Flow integration
  • kotlin-expert - Kotlin语言层面内容(@Immutable细节、StateFlow、密封类)
  • android-expert - Android导航、平台API
  • desktop-expert - Desktop导航、窗口管理、系统特性
  • kotlin-coroutines - 异步模式、Flow集成