mcpField
K
kotlin-multiplatform-expect-actual

kotlin-multiplatform-expect-actual

github.com/chrisbanes开发1.1k998 次安装1 天前更新
代码仓库

关于 kotlin-multiplatform-expect-actual

面向 Kotlin Multiplatform 开发者的边界设计技能,说明如何划分共享代码与平台实现。它指导开发者在 expect/actual 声明和接口之间做选择,并保持 common API 语义清晰且便于测试。

主要功能

  1. 01选择 expect/actual 或接口
  2. 02设计语义化的 common API
  3. 03将平台机制留在 actual 中
  4. 04Compose 平台叶节点指导
  5. 05跨源集验证实践

适用场景

  1. 01为平台能力设计共享 API
  2. 02为 common 代码添加可伪造依赖
  3. 03将平台 UI 与共享 Compose 屏幕分离
  4. 04检查 common 代码是否泄漏平台类型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/chrisbanes/skills --skill kotlin-multiplatform-expect-actual

安装后在 Claude Code 中输入/kotlin-multiplatform-expect-actual确认已加载。

详细介绍

Kotlin Multiplatform:expect/actual 边界

核心原则

让 common API 语义明确且稳定。将平台机制封装在简洁的 expect/actual 声明或接口后面,并将 Android/iOS/Desktop 的细节排除在 commonMain 之外。

边界流程

  1. 用通用术语描述产品能力:分享文本、读取剪贴板、请求触觉反馈、解析当前地区。
  2. 检查 common 调用方是否需要伪实现、注入依赖、管理生命周期,或在运行时选择实现。
  3. 从下表中选择最小的边界。
  4. 确保 common 签名不包含平台类型和平台术语。
  5. 将业务分支放在 common 代码中;将 actual/平台绑定作为转换层。
  6. 尽可能通过编译所有受影响的源集,并使用伪实现测试 common 代码来验证。

选择边界

情况优先选择
简单的编译时平台特化expect/actual 函数、值、类型别名或叶级可组合项
实现需要注入依赖、管理生命周期、运行时选择或测试伪实现common 接口加平台绑定
UI 大部分可共享,仅一个叶级部分不同由 common 可组合项调用 expect 叶级项
整个屏幕因平台而异在 common 导航契约后设置独立的平台屏幕
只有常量/资源不同提供语义值的 common API,以及各平台的 actual 值

让 common API 具有语义性

编写 common API 时,应让调用方描述意图,而非平台机制:

// GOOD: common API is semantic
expect fun currentRegion(): Region
// BAD: common API leaks Android implementation
expect fun currentRegionFromAndroidLocale(context: Context): Region

Android actual 可以使用 Locale API。iOS actual 可以使用 Foundation API。common 调用方不应了解这些细节。

保持 actual 精简

Actual 实现应将语义 API 转换为平台调用。如果操作需要 Activity、视图控制器、生命周期所有者、DI 或伪实现,请停止使用 expect class,改用由平台代码提供的接口:

// commonMain
interface ShareSheet {
    suspend fun shareText(text: String)
}
// androidMain
class AndroidShareSheet(
    private val activity: Activity,
) : ShareSheet {
    override suspend fun shareText(text: String) {
        val intent = Intent(Intent.ACTION_SEND)
            .setType("text/plain")
            .putExtra(Intent.EXTRA_TEXT, text)
        activity.startActivity(Intent.createChooser(intent, null))
    }
}

Android 实现明确由 Activity 管理。通用的 Context 往往会掩盖 UI 生命周期要求。应明确 suspend 的含义:对于许多平台 UI 操作,它表示“已启动分享表单”,而非“用户已完成分享”。

如果 actual 开始累积业务规则,就将这些规则移回 common 代码,只在 actual 中保留平台转换。

测试或 DI 很重要时优先使用接口

简单的编译时平台 API 使用 expect/actual。如果 common 代码需要伪实现、多个实现、运行时选择或生命周期管理,则使用接口:

interface Clipboard {
    suspend fun setText(text: String)
}

平台模块将 Clipboard 绑定到 Android/iOS 实现。common 测试使用伪实现。

Compose 专项指导

共享 UI 延伸到平台叶级部分时:

  1. 将平台专属的可组合项保持在叶节点。
  2. 对每个会发出 UI 的预期可组合项传递 Modifier。
  3. 拒绝在 commonMain 签名中使用平台类型(Context、Activity、Android 资源 ID、Uri、Bundle、UIViewController、NSBundle、平台权限枚举等)。
  4. 在平台 actual 中隐藏原生视图生命周期,并使用正确的互操作容器(AndroidView、UIKitView 等)。
  5. 不要直接从可组合项主体启动平台操作。在 actual 可组合项中使用 remember、LaunchedEffect、DisposableEffect 和稳定键,就像在 common Compose 代码中一样。
  6. 尽可能使用伪平台服务预览/测试 common 普通 UI 可组合项。

常见错误

错误修正方法
commonMain API 暴露 Android/iOS 类型替换为语义化的通用类型
expect 函数包含仅适用于某个平台的参数将这些细节移到 actual 中
各 actual 中重复编写业务分支将业务规则移到通用代码中
使用一个庞大的 Platform expect 对象按功能拆分:Clipboard、ShareSheet、Haptics
平台 UI 泄漏到组件树的较高层将平台专属的 Composable 下沉到叶节点
通用测试缺少可伪造的边界使用接口,而不是直接调用 expect
更改后只有一个目标能够编译完成前编译所有受影响的源集

评审中的危险信号

  • 通用代码导入平台包。
  • actual 实现了解产品状态、导航决策或领域规则。
  • 平台 API 名称出现在通用函数名中。
  • 添加第三个平台时需要修改通用调用方。
  • 仅为验证通用业务行为,测试就需要 Android/iOS 运行时。

相关内容(Compose / 共享 UI)

在此技能中专注于平台边界;像处理其他 Compose 目标一样接入共享 UI:

评论

支持 Markdown