# KMP Compose Sample

**版本信息**
| 组件 | 版本 |
| --- | --- |
| Kotlin | 2.2.21-ohos-01 |
| Compose Multiplatform | 1.9.2-eazytec-001 |
| Gradle | 8.14.3 |
| Android Gradle Plugin | 8.11.2 |
| Android SDK | Compile 36 / Min 24 / Target 36 |

一个使用 Kotlin Multiplatform + Compose Multiplatform 的示例项目，目标平台：Android、iOS。项目演示了按库分组的组件导航与属性效果展示，包括以下分组：
- `org.jetbrains.compose.ui:ui`
- `org.jetbrains.compose.foundation:foundation`
- `org.jetbrains.compose.material:material`
- `org.jetbrains.compose.material3:material3`

**首页为分组 → 组件列表，点击组件进入属性演示页，展示该组件的主要属性效果与交互。**

**功能特点**
- 分组导航：首页按四个分组展示组件/插件名称列表。
- 详情演示：进入组件页面，可通过控件调整属性并实时预览效果。
- 跨平台：共享 UI 代码，一套 Compose 在 Android/iOS 同步运行。
- 可扩展：每个分组支持新增更多组件演示页，Material 分组单独目录组织。

**目录结构**
- `composeApp/src/commonMain/kotlin/com/example/kmptpc_compose_sample/`
  - `App.kt`：应用入口，负责分组列表与页面导航。
  - `ComponentDemos.kt`：首页列表与各组件演示页的路由与部分演示实现。
  - `navigation/Navigation.kt`：分组模型、组件清单、页面模型（`Page`）。
  - `demos/material/`：Material 分组的演示页目录（建议每个组件一个文件）。
- `composeApp/src/androidMain/kotlin/com/example/kmptpc_compose_sample/MainActivity.kt`：Android 入口。
- `composeApp/src/iosMain/kotlin/com/example/kmptpc_compose_sample/MainViewController.kt`：iOS 入口控制器。
- `iosApp/iosApp/ContentView.swift`：在 SwiftUI 中桥接 `MainViewController()`。

**组件演示导航**
- 分组标题使用库坐标字符串（如 `org.jetbrains.compose.ui:ui`）。
- 组件列表点击后跳转到演示页面，页面包含：
  - 常用属性控制（启用状态、大小、颜色、形状、阴影等）
  - 示例交互（点击计数、滚动列表等）
- Material 分组包含但不限于以下演示页（目录：`demos/material/`）：
  - `AlertDialogSample.kt`
  - `AppBarSamples.kt`
  - `BackdropScaffoldSamples.kt`
  - `BadgeSamples.kt`
  - `BottomNavigationSamples.kt`
  - `BottomSheetScaffoldSamples.kt`
  - `ButtonSamples.kt`
  - `CardSamples.kt`
  - `ChipSamples.kt`
  - `ContentAlphaSamples.kt`
  - `DrawerSamples.kt`
  - `ElevationSamples.kt`
  - `ExposedDropdownMenuSamples.kt`
  - `FloatingActionButtonSamples.kt`
  - `IconButtonSamples.kt`
  - `ListSamples.kt`
  - `MenuSamples.kt`
  - `ModalBottomSheetSamples.kt`
  - `NavigationRailSample.kt`
  - `ProgressIndicatorSamples.kt`
  - `PullRefreshSamples.kt`
  - `ScaffoldSamples.kt`
  - `SelectionControlsSamples.kt`
  - `SliderSample.kt`
  - `SurfaceSamples.kt`
  - `SwipeableSamples.kt`
  - `SwipeToDismissSamples.kt`
  - `TabSamples.kt`
  - `TextFieldSamples.kt`
  - `TextSamples.kt`
  - `ThemeSamples.kt`

可按需要逐步补充/细化每个演示页的“全部属性效果”。

**依赖说明**
- Compose Runtime/Foundation/UI/Material3：已在 `composeApp/build.gradle.kts` 的 `commonMain` 依赖中。
- Material（旧版 Material 组件）：需在 `commonMain` 添加 `implementation(compose.material)`。
- 其他：AndroidX Lifecycle 等按需引用。

**运行与构建**
- Android（推荐在 Android Studio 运行，或使用命令行）：
  ```bash
  ./gradlew :composeApp:assembleDebug
  ```
- Windows:
  ```shell
  .\gradlew.bat :composeApp:assembleDebug
  ```

- iOS 应用构建与运行
  使用 IDE 工具栏中的运行配置来构建并运行 iOS 应用开发版本，或者在 Xcode 中打开 [/iosApp](./iosApp) 目录并运行。

- HarmonyOS (鸿蒙) 应用构建与运行
  1. 编译并发布 Shared Library 到鸿蒙工程目录：
     ```bash
     ./gradlew :composeApp:publishDebugBinariesToHarmonyApp
     ```
  2. 使用 DevEco Studio 打开 `harmonyApp` 目录。
  3. 在 DevEco Studio 中同步项目并运行到鸿蒙设备或模拟器。

---

Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…


## Material3 命名规范与常见错误

- 命名规范
  - 所有 Material3 示例函数统一命名为 `XXXSamplesM3()`，避免与 Material（M2）下同名函数导致的重载解析歧义。
  - 如需保留同名，可在调用处使用导入别名：
    - `import com.example...demos.material.AlertDialogSamples as AlertDialogSamplesM2`
    - `import com.example...demos.material3.AlertDialogSamples as AlertDialogSamplesM3`

- 必要导入
  - `import androidx.compose.ui.Modifier`
  - `import androidx.compose.ui.unit.dp`（需要尺寸单位时）
  - `import androidx.compose.ui.unit.sp`（需要字号单位时）
  - `import androidx.compose.foundation.layout.*`（需要 `Row/Column/Box/padding/size/Arrangement` 等）
  - 若使用对齐：`import androidx.compose.ui.Alignment`

- 实验性 API 选择加入
  - Material3 中部分组件（`ModalBottomSheet`、`DatePicker`、`TimePicker`、`SearchBar` 等）需要：
    - 文件顶部添加：`@file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)`

- 手势与滑动
  - Material（M2）可使用 `SwipeToDismiss`；Material3 版本可使用 `SwipeToDismissBox`（视版本）。
  - 基础手势层替代：`AnchoredDraggableState` + `anchoredDraggable`（foundation），避免使用已移除的 `swipeable` 系列。

## 目录结构（示例）
- `demos/ui/*`: UI 分组示例（如 `BoxDemo.kt`）
- `demos/foundation/*`: Foundation 分组示例（如 `BasicTextDemo.kt`, `LazyColumnDemo.kt`）
- `demos/material/*`: Material（M2）示例
- `demos/material3/*`: Material3 示例（统一 `XXXSamplesM3()` 命名）

## 导航与展示
- 首页按分组展示来自 `navigation/Navigation.kt` 的 `componentDemos` 列表。
- `DemoScreen(demo: ComponentDemo)` 根据 `demo.id` 路由到对应示例。
- Material3 示例全部接入，标题与路由统一。

## 常见错误
- `Unresolved reference 'Modifier'/'dp'/'sp'`：缺少必要导入，参考上文。
- `SmallTopAppBar` 未解析：使用 `TopAppBar` 等 M3 顶栏组件。
- 实验 API（`DatePicker`, `TimePicker`, `ModalBottomSheet`, `SearchBar` 等）：文件顶部添加
  - `@file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)`

## HarmonyOS 常见问题与解决方案

### 1. 运行时闪退：`ArkUIViewController_setId` 或 `Not mapped` 错误

**现象**：
App 启动或跳转到 Compose 页面时立即闪退，日志中包含 `ArkUIViewController_setId`、`Not mapped` 或 `libComposeApp.so` 相关错误。

**原因**：
1.  **符号解析错误**：Native 层的 `napi_init.cpp` 中未能正确加载 Compose 的初始化函数 `androidx_compose_ui_arkui_init`。
2.  **Controller 初始化时机**：在 Native 模块未加载完成前就尝试创建 `ArkUIViewController`，或者未在 `aboutToAppear` 生命周期中正确初始化。
3.  **Library Name 缺失**：在 ArkTS 的 `Compose` 组件中未指定正确的 `libraryName`。

**解决方案**：

1.  **修正 `napi_init.cpp`**：
    确保动态查找符号时包含回退机制（兼容不同版本的符号名称），并使用 `dlsym(RTLD_DEFAULT, ...)`。
    ```cpp
    // entry/src/main/cpp/napi_init.cpp
    typedef void (*init_fn)(napi_env, napi_value);
    
    static napi_value Init(napi_env env, napi_value exports) {
        // 尝试加载新版符号
        init_fn fn = reinterpret_cast<init_fn>(dlsym(RTLD_DEFAULT, "androidx_compose_ui_arkui_utils_init"));
        if (fn == nullptr) {
            // 回退到旧版符号
            fn = reinterpret_cast<init_fn>(dlsym(RTLD_DEFAULT, "androidx_compose_ui_arkui_init"));
        }
        if (fn != nullptr) {
            fn(env, exports);
        }
        // ...
    }
    ```

2.  **正确初始化 Controller (`Index.ets`)**：
    在 `aboutToAppear` 中检查 `nativeApi` 并初始化 Controller，添加异常捕获。
    ```typescript
    // entry/src/main/ets/pages/Index.ets
    aboutToAppear() {
      if (nativeApi) {
        try {
          this.controller = nativeApi.MainArkUIViewController();
        } catch (e) {
          hilog.error(DOMAIN, 'Compose', 'Controller creation failed: %{public}s', JSON.stringify(e));
        }
      }
    }
    ```

3.  **指定 Library Name**：
    在使用 `Compose` 组件时，明确传入 `libraryName`（通常为 `'entry'` 或你的动态库名称）。
    ```typescript
    Compose({
      controller: this.controller,
      libraryName: 'entry', // 必须指定
      onBackPressed: () => false
    })
    ```

### 2. 构建错误：`input file does not exist` (:cinteropResourceOhosArm64)

**原因**：`resource.def` 文件位置与 `build.gradle.kts` 中配置的路径不一致。
**解决**：确保文件位于 `src/ohosArm64Main/cinterop/resource.def`，或调整 `defFile(file(...))` 的路径配置。

### 3. 依赖冲突：Variant mismatch / 403 Forbidden

**原因**：部分 AndroidX 库（如 `lifecycle`, `savedstate`）尚未完全适配 OHOS 目标，导致 Gradle 解析时拉取错误的构件或无权限。
**解决**：在 `build.gradle.kts` 的 `ohos` 配置中排除冲突的组：
```kotlin
configurations.all {
    if (name.contains("ohos", ignoreCase = true)) {
        exclude(group = "org.jetbrains.androidx.lifecycle")
        exclude(group = "org.jetbrains.androidx.savedstate")
    }
}
```

## Gradle 构建常见问题与避坑指南

### 1. `Unresolved reference` 错误
**现象**：`build.gradle.kts` 报错 `Unresolved reference: libs.xxx`。
**原因**：`libs.versions.toml` 中未定义对应的 library 或 plugin 别名，或者别名拼写不一致（Gradle 会将 TOML 中的 `-` 转换为 `.`）。
**解决**：
- 检查 `libs.versions.toml` 中的 `[libraries]` 和 `[plugins]` 块。
- 确认别名拼写。例如 `compose-multiplatform-export` 在 Kotlin DSL 中应通过 `libs.compose.multiplatform.export` 引用。

### 2. Skiko 版本不兼容
**现象**：OHOS 运行时崩溃或渲染异常，提示 Skiko 版本问题。
**原因**：Compose Multiplatform 默认依赖的 Skiko 版本可能未适配特定的 OHOS 版本。
**解决**：
在 `build.gradle.kts` 中强制指定 Skiko 版本：
```kotlin
resolutionStrategy {
    eachDependency {
        if (requested.group == "org.jetbrains.skiko" && requested.name == "skiko") {
             if (configName.contains("ohos", ignoreCase = true)) {
                 useVersion("0.9.22.2-ohos-001") // 使用适配的版本
             }
        }
    }
}
```

### 3. 依赖冲突：`androidx.collection`
**现象**：构建失败，提示依赖版本冲突。
**原因**：不同库依赖了不同版本的 `androidx.collection`。
**解决**：
强制指定版本并排除冲突模块：
```kotlin
configurations.all {
    resolutionStrategy {
        eachDependency {
            if (requested.group == "androidx.collection" && requested.name == "collection") {
                useVersion(libs.versions.androidx.collection.get())
            }
        }
    }
    exclude(group = "androidx.collection", module = "collection-jvm")
}
```
