compose-multiplatform

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Compose Multiplatform

Compose Multiplatform

Compose Multiplatform (CMP) and Kotlin Multiplatform (KMP) patterns for cross-platform UI. Loaded for projects with
org.jetbrains.compose
plugin. Foundation:
../compose-motion/SKILL.md
for animation API; this file covers what's specific to writing one Compose codebase for Android + iOS + Desktop + Web.

适用于跨平台UI的Compose Multiplatform (CMP) 和Kotlin Multiplatform (KMP) 模式。 适用于使用
org.jetbrains.compose
插件的项目。 基础内容:动画API请参考
../compose-motion/SKILL.md
;本文档涵盖为Android + iOS + Desktop + Web编写单一Compose代码库的特定内容。

KMP vs CMP - quick clarification

KMP vs CMP - 快速说明

KMP (Kotlin Multiplatform) is the language and build infrastructure: shared Kotlin code compiled to JVM, Native (iOS, macOS, Linux, Windows), and Wasm. CMP (Compose Multiplatform) is the UI framework on top of KMP, built by JetBrains as a port of Jetpack Compose. You write a single Compose codebase in
commonMain
that runs on Android, iOS, Desktop (JVM), and Web (Wasm). Platform-specific code lives in
androidMain
,
iosMain
,
desktopMain
,
wasmJsMain
and is wired in via
expect
/
actual
declarations.

KMP(Kotlin Multiplatform)是语言与构建基础设施:共享Kotlin代码可编译为JVM、Native(iOS、macOS、Linux、Windows)和Wasm。CMP(Compose Multiplatform)是构建在KMP之上的UI框架,由JetBrains作为Jetpack Compose的移植版本开发。您在
commonMain
中编写单一Compose代码库,即可在Android、iOS、Desktop(JVM)和Web(Wasm)上运行。平台特定代码存放在
androidMain
iosMain
desktopMain
wasmJsMain
中,并通过
expect
/
actual
声明进行关联。

Project structure

项目结构

composeApp/
├── src/
│   ├── commonMain/        ← shared Compose code (most of the app)
│   │   └── kotlin/
│   ├── androidMain/       ← Android-specific (uses Activity, Context)
│   ├── iosMain/           ← iOS-specific (uses UIKit/UIView interop)
│   ├── desktopMain/       ← JVM desktop (uses java.awt/swing if needed)
│   └── wasmJsMain/        ← Wasm web target
├── build.gradle.kts
iosApp/                    ← Xcode project consuming the generated framework
androidApp/                ← Android Application module (often merged into composeApp)
The
commonMain
folder should hold 80-95% of your code in a well-architected CMP project. If
iosMain
or
androidMain
start growing past a few hundred lines, you're probably leaking platform concerns into UI logic that could stay shared.

composeApp/
├── src/
│   ├── commonMain/        ← 共享Compose代码(应用的核心部分)
│   │   └── kotlin/
│   ├── androidMain/       ← Android特定代码(使用Activity、Context)
│   ├── iosMain/           ← iOS特定代码(使用UIKit/UIView互操作)
│   ├── desktopMain/       ← JVM桌面代码(必要时使用java.awt/swing)
│   └── wasmJsMain/        ← Wasm Web目标代码
├── build.gradle.kts
iosApp/                    ← 消费生成框架的Xcode项目
androidApp/                ← Android应用模块(通常合并到composeApp中)
在架构良好的CMP项目中,
commonMain
文件夹应包含80-95%的代码。如果
iosMain
androidMain
的代码量超过几百行,您可能是将平台相关的逻辑泄漏到了本可以保持共享的UI逻辑中。

expect
/
actual
pattern

expect
/
actual
模式

The KMP escape hatch when you genuinely need different implementations per target. Declare the contract once in
commonMain
, implement it once per target.
kotlin
// commonMain
expect fun openShareSheet(text: String)

// androidMain
actual fun openShareSheet(text: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    context.startActivity(Intent.createChooser(intent, null))
}

// iosMain
actual fun openShareSheet(text: String) {
    val activityVC = UIActivityViewController(
        activityItems = listOf(text),
        applicationActivities = null
    )
    UIApplication.sharedApplication.keyWindow
        ?.rootViewController
        ?.presentViewController(activityVC, true, null)
}
expect
/
actual
works for top-level functions, classes, type aliases, and properties. The signature in
actual
must match exactly, including modifiers and default values.

当您确实需要为每个目标提供不同实现时,KMP的“逃生舱”机制。在
commonMain
中声明一次契约,为每个目标实现一次。
kotlin
// commonMain
expect fun openShareSheet(text: String)

// androidMain
actual fun openShareSheet(text: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    context.startActivity(Intent.createChooser(intent, null))
}

// iosMain
actual fun openShareSheet(text: String) {
    val activityVC = UIActivityViewController(
        activityItems = listOf(text),
        applicationActivities = null
    )
    UIApplication.sharedApplication.keyWindow
        ?.rootViewController
        ?.presentViewController(activityVC, true, null)
}
expect
/
actual
适用于顶层函数、类、类型别名和属性。
actual
中的签名必须与
expect
完全匹配,包括修饰符和默认值。

expect
/
actual
for composables

用于可组合项的
expect
/
actual

Composables follow the same rules. Useful when a feature needs a platform-specific Compose API (Android
RuntimeShader
, iOS
UIKitView
, Desktop
SwingPanel
).
kotlin
// commonMain
@Composable
expect fun PlatformBlur(modifier: Modifier = Modifier, content: @Composable () -> Unit)

// androidMain (uses RuntimeShader on Android 13+)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
    Box(modifier.graphicsLayer { renderEffect = blurEffect }) { content() }
}

// iosMain (uses UIVisualEffectView via UIKitView)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
    Box(modifier) {
        UIKitView(
            factory = { UIVisualEffectView(effect = UIBlurEffect.systemMaterial()) },
            modifier = Modifier.matchParentSize()
        )
        content()
    }
}
Rule:
expect
composables should be the exception, not the rule. Most "platform feel" differences can be tuned via tokens (colors, corner radii, spring stiffness) in
commonMain
, not via separate code paths.

可组合项遵循相同的规则。当某个功能需要特定平台的Compose API(如Android的
RuntimeShader
、iOS的
UIKitView
、Desktop的
SwingPanel
)时非常有用。
kotlin
// commonMain
@Composable
expect fun PlatformBlur(modifier: Modifier = Modifier, content: @Composable () -> Unit)

// androidMain(在Android 13+上使用RuntimeShader)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
    Box(modifier.graphicsLayer { renderEffect = blurEffect }) { content() }
}

// iosMain(通过UIKitView使用UIVisualEffectView)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
    Box(modifier) {
        UIKitView(
            factory = { UIVisualEffectView(effect = UIBlurEffect.systemMaterial()) },
            modifier = Modifier.matchParentSize()
        )
        content()
    }
}
规则:
expect
可组合项应是例外情况,而非常态。大多数“平台风格”差异可以通过
commonMain
中的标记(颜色、圆角半径、弹簧刚度)进行调整,而非通过单独的代码路径。

LocalDensity
cross-platform

跨平台的
LocalDensity

On Android,
LocalDensity.current.density
reflects the device DPI bucket (1.0, 1.5, 2.0, 3.0...). On iOS, density is computed from
UIScreen.scale
(typically 2.0 or 3.0 on Retina). On Desktop, density depends on the screen scaling factor (1.0 by default; 2.0 on Retina-class displays; user-configurable on Windows). On Wasm, density follows
window.devicePixelRatio
.
Don't hardcode
Dp
to pixel ratios; trust
Dp
and
LocalDensity
to handle conversion. If you need an exact pixel value (e.g., for a
Canvas
draw operation), do the conversion explicitly:
kotlin
val density = LocalDensity.current
val pxValue = with(density) { 16.dp.toPx() }
Avoid reading
density
inside hot loops; cache the conversion.

在Android上,
LocalDensity.current.density
反映设备的DPI等级(1.0、1.5、2.0、3.0...)。在iOS上,密度由
UIScreen.scale
计算得出(Retina屏幕通常为2.0或3.0)。在Desktop上,密度取决于屏幕缩放因子(默认1.0;Retina级显示器为2.0;Windows上可由用户配置)。在Wasm上,密度遵循
window.devicePixelRatio
不要将
Dp
硬编码为像素比率;请信任
Dp
LocalDensity
来处理转换。如果您需要精确的像素值(例如,用于
Canvas
绘制操作),请显式进行转换:
kotlin
val density = LocalDensity.current
val pxValue = with(density) { 16.dp.toPx() }
避免在热循环中读取
density
;请缓存转换结果。

LocalConfiguration
and platform-aware UI

LocalConfiguration
与平台感知UI

LocalConfiguration.current
is Android-only and lives in
androidMain
. For CMP, prefer the cross-platform alternatives:
  • LocalWindowInfo.current.containerSize
    - the window/screen size as
    IntSize
    , available in
    commonMain
    .
  • LocalDensity.current
    - density, available in
    commonMain
    .
  • LocalLayoutDirection.current
    - LTR / RTL.
  • BoxWithConstraints { ... }
    - read
    maxWidth
    /
    maxHeight
    directly inside layout.
If you need real device characteristics (orientation, idiom, model), wrap the access in
expect
/
actual
and pass a typed object like
PlatformInfo
to the common layer.

LocalConfiguration.current
仅Android可用的API,位于
androidMain
中。对于CMP,建议使用跨平台替代方案:
  • LocalWindowInfo.current.containerSize
    - 窗口/屏幕尺寸,类型为
    IntSize
    ,可在
    commonMain
    中使用。
  • LocalDensity.current
    - 密度,可在
    commonMain
    中使用。
  • LocalLayoutDirection.current
    - 从左到右(LTR)/从右到左(RTL)。
  • BoxWithConstraints { ... }
    - 在布局中直接读取
    maxWidth
    /
    maxHeight
如果您需要真实的设备特性(方向、设备类型、型号),请将访问逻辑包装在
expect
/
actual
中,并将类型化对象(如
PlatformInfo
)传递到共享层。

Fonts cross-platform via Compose Resources

通过Compose Resources实现跨平台字体

org.jetbrains.compose.resources
is the shared resources plugin. Drop fonts in
commonMain/composeResources/font/
, and the Gradle plugin generates a typed
Res
accessor.
composeApp/src/commonMain/composeResources/
├── font/
│   ├── Inter-Regular.ttf
│   └── Inter-Bold.ttf
├── drawable/
│   └── logo.svg
└── values/
    ├── strings.xml          ← default locale
    └── strings.fr.xml       ← French overrides
Usage in
commonMain
:
kotlin
import myproject.composeapp.generated.resources.Inter_Regular
import myproject.composeapp.generated.resources.Inter_Bold
import myproject.composeapp.generated.resources.Res

val InterFamily = FontFamily(
    Font(Res.font.Inter_Regular, FontWeight.Normal),
    Font(Res.font.Inter_Bold, FontWeight.Bold),
)

Text("Hello", fontFamily = InterFamily)
Same pattern for
Res.drawable.logo
(image),
Res.string.app_name
(localized string via
stringResource(...)
),
Res.file.config
(raw bytes via
Res.readBytes(...)
).

org.jetbrains.compose.resources
是共享资源插件。将字体放入
commonMain/composeResources/font/
中,Gradle插件会生成类型化的
Res
访问器。
composeApp/src/commonMain/composeResources/
├── font/
│   ├── Inter-Regular.ttf
│   └── Inter-Bold.ttf
├── drawable/
│   └── logo.svg
└── values/
    ├── strings.xml          ← 默认语言环境
    └── strings.fr.xml       ← 法语覆盖
commonMain
中的用法:
kotlin
import myproject.composeapp.generated.resources.Inter_Regular
import myproject.composeapp.generated.resources.Inter_Bold
import myproject.composeapp.generated.resources.Res

val InterFamily = FontFamily(
    Font(Res.font.Inter_Regular, FontWeight.Normal),
    Font(Res.font.Inter_Bold, FontWeight.Bold),
)

Text("Hello", fontFamily = InterFamily)
相同模式适用于
Res.drawable.logo
(图片)、
Res.string.app_name
(通过
stringResource(...)
实现本地化字符串)、
Res.file.config
(通过
Res.readBytes(...)
获取原始字节)。

iOS interop with SwiftUI

与SwiftUI的iOS互操作

CMP produces a
UIViewController
you can drop into a SwiftUI app. KMP generates a top-level Kotlin function (commonly named
MainViewController()
or
ComposeUIViewController { ... }
) that returns a
UIViewController
. Wrap it with
UIViewControllerRepresentable
.
kotlin
// iosMain/kotlin/main.ios.kt
fun MainViewController(): UIViewController = ComposeUIViewController {
    AppContent()  // commonMain composable
}
swift
// iOS app target
import SwiftUI
import ComposeApp  // KMP-generated framework

struct ComposeContent: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        Main_iosKt.MainViewController()
    }
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    var body: some View { ComposeContent().ignoresSafeArea() }
}
The Kotlin function name gets mangled to
Main_iosKt.MainViewController()
because the file is
main.ios.kt
. Check the generated framework headers if the symbol name surprises you.

CMP会生成一个
UIViewController
,您可以将其嵌入到SwiftUI应用中。KMP会生成一个顶层Kotlin函数(通常命名为
MainViewController()
ComposeUIViewController { ... }
),返回一个
UIViewController
。使用
UIViewControllerRepresentable
进行包装。
kotlin
// iosMain/kotlin/main.ios.kt
fun MainViewController(): UIViewController = ComposeUIViewController {
    AppContent()  // commonMain中的可组合项
}
swift
// iOS应用目标
import SwiftUI
import ComposeApp  // KMP生成的框架

struct ComposeContent: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        Main_iosKt.MainViewController()
    }
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    var body: some View { ComposeContent().ignoresSafeArea() }
}
由于文件名为
main.ios.kt
,Kotlin函数名会被修改为
Main_iosKt.MainViewController()
。如果符号名称不符合预期,请查看生成的框架头文件。

Android entry point

Android入口点

No interop ceremony on Android. The Activity hosts the common composable directly via
setContent { ... }
.
kotlin
// androidApp/src/main/kotlin/MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppContent()  // commonMain composable
        }
    }
}
If you need to pass
Context
or
Activity
into
commonMain
, expose it via a DI graph or an
expect class PlatformContext
in
commonMain
with
actual class PlatformContext(val context: Context)
in
androidMain
.

Android无需互操作仪式。Activity直接通过
setContent { ... }
托管共享可组合项。
kotlin
// androidApp/src/main/kotlin/MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppContent()  // commonMain中的可组合项
        }
    }
}
如果您需要将
Context
Activity
传递到
commonMain
中,请通过DI图或在
commonMain
中暴露
expect class PlatformContext
,并在
androidMain
中实现
actual class PlatformContext(val context: Context)

Embedding SwiftUI/UIKit inside a Compose iOS view (the reverse direction)

在Compose iOS视图中嵌入SwiftUI/UIKit(反向操作)

Use
UIKitView
for a
UIView
factory or
UIKitViewController
for a
UIViewController
factory.
kotlin
// iosMain
UIKitView(
    factory = {
        UISwitch().apply {
            addTarget(target, action = NSSelectorFromString("onToggle:"), forControlEvents = UIControlEventValueChanged)
        }
    },
    modifier = Modifier.size(48.dp, 32.dp)
)
For SwiftUI views: wrap them in a
UIHostingController
exposed via a Swift
@objc
bridge function, then call from Kotlin via the generated headers (cinterop). See
references/cmp-interop.md
for the full pattern.

对于
UIView
工厂,使用
UIKitView
;对于
UIViewController
工厂,使用
UIKitViewController
kotlin
// iosMain
UIKitView(
    factory = {
        UISwitch().apply {
            addTarget(target, action = NSSelectorFromString("onToggle:"), forControlEvents = UIControlEventValueChanged)
        }
    },
    modifier = Modifier.size(48.dp, 32.dp)
)
对于SwiftUI视图:将它们包装在通过Swift
@objc
桥接函数暴露的
UIHostingController
中,然后通过生成的头文件(cinterop)从Kotlin调用。完整模式请参考
references/cmp-interop.md

Animation cross-platform

跨平台动画

All animation APIs (
animate*AsState
,
AnimatedVisibility
,
updateTransition
,
SharedTransitionLayout
) work identically across targets in CMP 1.7+. Spring tuning written in
commonMain
produces the same physics on Android and iOS. Gestures (
Modifier.draggable
,
Modifier.pointerInput
) work cross-platform with the same API surface.
The animation primer lives in
../compose-motion/SKILL.md
. Cross-platform deltas to keep in mind:
  • iOS first-frame is slower (Skia bootstrap); a 200ms enter animation feels tighter on Android, slightly delayed on iOS cold start.
  • Wasm motion can stutter on first frame (JIT warmup); pre-warm critical paths or hide motion until interactive.

所有动画API(
animate*AsState
AnimatedVisibility
updateTransition
SharedTransitionLayout
)在CMP 1.7+中跨目标的工作方式完全相同。在
commonMain
中编写的弹簧调整参数会在Android和iOS上产生相同的物理效果。手势(
Modifier.draggable
Modifier.pointerInput
)跨平台使用相同的API表面。
动画入门内容请参考
../compose-motion/SKILL.md
。需要注意的跨平台差异:
  • iOS第一帧速度较慢(Skia初始化);200ms的入场动画在Android上感觉紧凑,在iOS冷启动时略有延迟。
  • Wasm动画在第一帧可能出现卡顿(JIT预热);预热关键路径或在交互前隐藏动画。

What does NOT work (gotchas)

无法正常工作的内容(注意事项)

  • Drawer state on iOS: native
    ModalNavigationDrawer
    swipe-to-open from the leading edge conflicts with iOS's back-swipe gesture. Use a button trigger or move the swipe area inward 30dp+.
  • LayoutDirection.Rtl
    quirks
    : Android handles RTL natively, iOS Compose had bugs in 1.6 (text alignment, padding inversions). Improved in 1.7+ but verify with real Arabic/Hebrew strings.
  • Soft keyboard handling:
    imePadding()
    works on Android out of the box. On iOS Compose 1.6+, it requires
    IOSKeyboardEventListener
    setup or a
    WindowInsets
    observer wired through the platform layer.
  • Color.parseHex(...)
    does not exist in Compose. Use
    Color(0xFFRRGGBB)
    or write a tiny extension.
  • System fonts on iOS via Compose: do not fallback to
    FontFamily.SansSerif
    and expect SF Pro. Compose on iOS ships its own font fallback chain. Either bundle SF Pro via Compose Resources (license-permitting) or use
    UIKitView
    to drop a native
    UILabel
    for system-font text.
  • Animations on Web (Wasm): heavier startup, occasional first-frame stutter; profile with browser devtools and lazy-load heavy animation graphs.
  • java.util.UUID
    ,
    java.io.File
    and other JVM-only APIs are forbidden in
    commonMain
    if you ship to iOS or Wasm. Use
    kotlinx.uuid
    ,
    kotlinx-io
    , or the
    okio
    multiplatform port.

  • iOS上的抽屉状态:原生
    ModalNavigationDrawer
    从左侧边缘滑动打开的手势与iOS的返回滑动手势冲突。使用按钮触发或将滑动区域向内移动30dp以上。
  • LayoutDirection.Rtl
    的问题
    :Android原生支持RTL,iOS Compose在1.6版本中存在bug(文本对齐、内边距反转)。1.7+版本有所改进,但需使用真实的阿拉伯语/希伯来语文本进行验证。
  • 软键盘处理
    imePadding()
    在Android上开箱即用。在iOS Compose 1.6+中,需要设置
    IOSKeyboardEventListener
    或通过平台层关联
    WindowInsets
    观察者。
  • **
    Color.parseHex(...)
    **在Compose中不存在。使用
    Color(0xFFRRGGBB)
    或编写一个小型扩展函数。
  • 通过Compose使用iOS系统字体:不要依赖
    FontFamily.SansSerif
    并期望获得SF Pro字体。iOS上的Compose有自己的字体回退链。要么通过Compose Resources捆绑SF Pro(需符合许可),要么使用
    UIKitView
    嵌入原生
    UILabel
    来显示系统字体文本。
  • Web(Wasm)上的动画:启动负载较重,偶尔第一帧卡顿;使用浏览器开发者工具分析性能,并通过
    kotlinx.coroutines
    延迟组合来懒加载次要屏幕。
  • **
    java.util.UUID
    java.io.File
    **等仅JVM可用的API在
    commonMain
    中是禁止的,如果您要发布到iOS或Wasm。请使用
    kotlinx.uuid
    kotlinx-io
    okio
    的多平台移植版本。

CMP version notes

CMP版本说明

  • Compose Multiplatform 1.7 stable:
    SharedTransitionLayout
    cross-platform, improved iOS keyboard handling, lifecycle observability via
    LocalLifecycleOwner
    on iOS.
  • Kotlin 2.0+ required (K2 compiler).
  • Some Material 3 components have platform-specific look (e.g.,
    Switch
    on iOS auto-renders with iOS-style proportions;
    DatePicker
    stays Material across all targets).
  • compose-multiplatform-resources
    plugin is the standard for assets; the older
    moko-resources
    is no longer recommended for new projects.

  • Compose Multiplatform 1.7稳定版:支持跨平台
    SharedTransitionLayout
    ,改进了iOS键盘处理,iOS上可通过
    LocalLifecycleOwner
    观察生命周期。
  • 需要Kotlin 2.0+(K2编译器)。
  • 部分Material 3组件具有特定平台的外观(例如,iOS上的
    Switch
    会自动渲染为iOS风格的比例;
    DatePicker
    在所有目标上保持Material风格)。
  • compose-multiplatform-resources
    插件是资源管理的标准;旧版
    moko-resources
    不再推荐用于新项目。

Performance considerations

性能考虑

  • iOS first-frame is slower than Android (Skia bootstrapping ~150-300ms cold). Keep your splash visible until the first composition emits, or pre-warm with a transparent root composable.
  • Wasm bundle size: aim for <2MB compressed. Tree-shake heavy deps, lazy-load secondary screens via
    kotlinx.coroutines
    deferred composition, and inspect the
    .wasm
    output in
    wasmJsBrowserDistribution
    .
  • Desktop: cold start is fast on JVM; AOT compilation via Kotlin/Native is overkill for desktop unless you need a single-file binary.
  • Android: same baseline as Jetpack Compose - profile with the Compose compiler stability metrics and
    Layout Inspector
    recomposition counts.

  • iOS第一帧比Android慢(Skia初始化约150-300ms冷启动)。保持闪屏可见直到第一次组合完成,或使用透明根可组合项进行预热。
  • Wasm包大小:目标压缩后小于2MB。摇树优化重型依赖,通过
    kotlinx.coroutines
    延迟组合懒加载次要屏幕,并在
    wasmJsBrowserDistribution
    中检查
    .wasm
    输出。
  • Desktop:JVM冷启动速度快;除非需要单文件二进制,否则通过Kotlin/Native进行AOT编译对于桌面来说是过度优化。
  • Android:与Jetpack Compose基线相同——使用Compose编译器稳定性指标和
    Layout Inspector
    重组计数进行性能分析。

Anti-Patterns

反模式

BADGOODWhy
Reflection trick or
System.getProperty("os.name")
to detect platform inside
commonMain
expect
/
actual
with a typed
Platform
object
Reflection breaks on Wasm/Native;
expect
/
actual
is the contract the compiler enforces
Assuming Android
Context
is reachable in
commonMain
Inject a typed dependency via
expect class PlatformContext
or a DI scope
Context
does not exist on iOS/Desktop/Wasm; the code will not compile for those targets
Hardcoding Material colors that look great on Android but jarring on iOSDefine a
commonMain
design system, then optionally adjust 2-3 tokens via
actual
Cross-platform consistency is good, but iOS users notice when a Material blue feels alien on iPhone
LaunchedEffect(Unit) { while(true) { delay(16); ... } }
in
commonMain
rememberInfiniteTransition()
or scope to lifecycle events
Tight coroutine loops drain battery on iOS; infinite transitions pause when offscreen

错误做法正确做法原因
commonMain
中使用反射技巧或
System.getProperty("os.name")
检测平台
使用
expect
/
actual
和类型化
Platform
对象
反射在Wasm/Native上会失效;
expect
/
actual
是编译器强制执行的契约
假设Android的
Context
commonMain
中可访问
通过
expect class PlatformContext
或DI作用域注入类型化依赖
Context
在iOS/Desktop/Wasm上不存在;代码在这些目标上无法编译
硬编码在Android上美观但在iOS上不协调的Material颜色
commonMain
中定义设计系统,然后可选地通过
actual
调整2-3个标记
跨平台一致性是好事,但iOS用户会注意到Material蓝色在iPhone上显得格格不入
commonMain
中使用
LaunchedEffect(Unit) { while(true) { delay(16); ... } }
使用
rememberInfiniteTransition()
或关联到生命周期事件
紧密的协程循环会消耗iOS电池;无限过渡在屏幕外时会暂停

Quick Reference: Loading Sub-skills

快速参考:加载子技能

NeedLoad
iOS / Android interop deep-dive
references/cmp-interop.md
Per-platform behavior catalog
references/cmp-platform-quirks.md
Animation API
../compose-motion/SKILL.md
Advanced graphics (M3 Expressive, AGSL on Android only)
../compose-graphics/SKILL.md
iOS-side native interop with SwiftUI
../swiftui-motion/SKILL.md
(when target is iOS and SwiftUI native blend wanted)
Mobile UX context
../mobile-principles/SKILL.md
Desktop UX context
../desktop-principles/SKILL.md
Foundation
../motion-principles/SKILL.md

需求加载内容
iOS / Android互操作深入讲解
references/cmp-interop.md
各平台行为目录
references/cmp-platform-quirks.md
动画API
../compose-motion/SKILL.md
高级图形(M3 Expressive、仅Android可用的AGSL)
../compose-graphics/SKILL.md
与SwiftUI的iOS原生互操作
../swiftui-motion/SKILL.md
(当目标是iOS且需要混合SwiftUI原生时)
移动UX背景
../mobile-principles/SKILL.md
桌面UX背景
../desktop-principles/SKILL.md
基础内容
../motion-principles/SKILL.md

Sources

资料来源