项目保持 shared、androidApp、iosApp 三个构建模块。shared 继续输出单一 iOS framework,业务隔离采用 feature 包和目录,不增加只做转发的 Gradle 模块。
Android/iOS Scene → shared feature presentation → Repository → RemoteDataSource / Cache
↓
domain model
- UI 只观察
UiState、收集 Effect 并提交用户操作。 - ViewModel 不依赖平台 UI、路由、DTO 或 MMKV 类型。
- Repository 只依赖所属业务的窄
RemoteDataSource契约和必要缓存。 - DTO 只存在于
data/remote/dto,进入 Repository 前必须映射为领域模型。 - 领域模型不保存平台对象或展示文案。
大白话:KMP 层管协议,不管传输;封不封装,看业务重不重。 判断就两问:这段逻辑换个平台还成立吗?成立的是决策(协议、规则、时序),进 commonMain,不成立的是执行(系统调用),留在平台层;业务重不重?重才建接口,轻就各写各的。
- 协议性逻辑进共享层:分页状态机、错误映射、会话规则、硬件协议编解码与指令时序,两端的业务表现必须一致。
- 执行留在平台边界:权限请求、系统分享、SDK 桥接由两端各自实现,共享层只接收结果继续业务;权限不建共享抽象。
- 重业务的接口形态:
commonMain声明契约,Android 实现放androidApp,iOS 实现可放shared/iosMain或iosApp,经initKoin的extraModules注入。蓝牙这类:KMP 层只封装硬件协议(字节怎么拼、流程怎么走),怎么发、用哪些系统类是平台的事;上层调共享层暴露的业务动作(如syncData()),不碰裸传输。 - 轻业务的判断:共享的只剩一行约定(如权限只剩一个 Boolean)就不值得建接口,两端各写几行,拿不准就先不封。
- 自建桥之前先查现成 KMP 库:平台差异能被库吃掉就不写桥(存储已用 MMKV-KMP,BLE 可评估 Kable)。
- 封装时机是重复出现时,不是预见时;从轻到重提取接口随时可行,反向拆分成本更高。
- 保持零
expect/actual:仅当接口与库都无法覆盖时再评估。
core/<capability>:网络、缓存、分页、会话和通用 presentation 契约。domain/model/<feature>:跨数据与 presentation 使用的领域模型。data/<feature>/<Feature>Repository.kt:该业务数据的唯一入口。data/remote/<Feature>RemoteDataSource.kt:Repository 所需的最小远端能力。data/remote/dto/<Name>Dto.kt:传输模型与纯映射函数。feature/<feature>/presentation/<Feature>UiState.kt、<Feature>Effect.kt、<Feature>ViewModel.kt。
- 页面使用
<Feature>Scene,放在feature/<feature>/ui。 - Scene 只负责状态绑定、交互分发和页面编排。
- 编辑器、表单、列表项和加载内容按职责拆成独立文件。
- 跨 feature 复用的 UI 放在
ui/components或明确语义的ui/<domain>。
- 页面使用
<Feature>Scene,放在Features/<Feature>。 Application只保存应用根 Scene,Navigation只保存 Router 和 Route。- 原生 SDK 桥接放在
Infrastructure或所属 feature 的 Representable 文件。 - 跨 feature 复用 View 放在
UI/Components。
注释使用中文,只说明类型职责、边界条件、兼容原因或非显然行为,不逐行复述实现。