ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Kotlin Multiplatform(KMM)跨平台实战指南:基于 agentic-awesome-skills 的共享业务逻辑架构参考

Kotlin Multiplatform(KMM)跨平台实战指南:基于 agentic-awesome-skills 的共享业务逻辑架构参考 Kotlin MultiplatformKMM跨平台实战指南基于 agentic-awesome-skills 的共享业务逻辑架构参考【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,115 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills导读本文是 agentic-awesome-skills 仓库中 Android 开发技能android-dev针对 Kotlin MultiplatformKMM技术栈的深度参考文档。它解决的核心问题非常聚焦如何在 Android 与 iOS乃至 Desktop、Web之间共享领域与数据层业务逻辑同时保留各平台的原生 UI。读完本文你将掌握一套可落地的 KMM 工程骨架——从 shared 模块目录划分、expect/actual机制下的 Ktor 网络层、SQLDelight 本地数据库到共享 Repository、Flow 状态流、Koin 依赖注入与 Gradle 多目标配置并了解 Compose Multiplatform 何时介入共享 UI。全文以仓库文档plugins/agentic-awesome-skills-claude/skills/android-dev/references/kmm.md为主体骨架结合技能总纲与详细指南中的定位与最佳实践展开。一、KMM 在项目中的定位为什么选它在正式进入代码之前先明确 KMM 在整个技术选型中的位置。仓库中的 android-dev 技能SKILL.md与详细指南detailed-guide.md给出了清晰的选型建议语言全栈 Kotlin——共享模块与 Android 端使用同一语言团队心智负担最小UIAndroid 端使用原生 Jetpack Compose桌面/Web 端可选用 Compose Multiplatform 共享 UI核心库栈Ktor网络、SQLDelight本地数据库、Koin依赖注入、kotlinx.serialization序列化、Napier跨平台日志最适合的场景在 Android Desktop Web 之间共享业务逻辑同时保留 Android 原生 UI对应详细指南 §1 决策矩阵中的 Shared business logic only → KMM ✅ Best。值得注意的边界详细指南 §1 明确提醒若项目需要Android Web 全栈 UI 共享Flutter 或 Hybrid 可能更合适KMM 的强项是共享逻辑 各自原生 UI而非一套 UI 打天下。选型时应对照决策矩阵避免拿 KMM 去做它不擅长的像素级自定义 UI虽然 Compose Multiplatform 也能做但成本不同。二、标准 KMM 工程结构shared 模块 平台壳工程KMM 工程的骨架由两部分组成一个承载共享代码的shared模块以及各平台的应用壳如androidApp。仓库参考文档给出了如下标准布局project/ ├── shared/ # Shared KMM module │ ├── src/ │ │ ├── commonMain/kotlin/ # Business logic, domain, data │ │ │ ├── domain/ │ │ │ │ ├── model/ │ │ │ │ ├── repository/ # Interfaces │ │ │ │ └── usecase/ │ │ │ ├── data/ │ │ │ │ ├── remote/ # Ktor client DTOs │ │ │ │ ├── local/ # SQLDelight DAOs │ │ │ │ └── repository/ # Implementations │ │ │ └── di/ # Koin modules │ │ ├── androidMain/kotlin/ # Android-specific actual implementations │ │ └── iosMain/kotlin/ # iOS-specific actual (if needed) │ └── build.gradle.kts ├── androidApp/ # Android app module │ ├── src/main/java/ │ │ ├── ui/ # Jetpack Compose screens │ │ ├── presentation/ # Android ViewModels │ │ └── di/ # Android-specific DI │ └── build.gradle.kts └── build.gradle.kts该结构与详细指南 §2 推荐的 Clean Architecture 分层完全同构commonMain/kotlin/domainmodel / repository 接口 / usecase→ 对应 Clean Architecture 的 domain 层commonMain/kotlin/dataremote / local / repository 实现→ 对应 data 层commonMain/kotlin/diKoin 模块→ 对应依赖注入层androidApp内的ui、presentation、di→ 对应展示层。共享模块遵循接口在 domain、实现在 data的原则domain/repository只放接口具体实现放在data/repository中由依赖注入在运行时装配。这样 commonMain 的代码不依赖任何平台 SDKiOS 端只需提供iosMain的少量actual实现如数据库驱动即可复用全部业务逻辑。三、跨平台网络层Ktor HTTP Client 与 expect/actual网络层是共享逻辑中最典型的跨平台环节。参考文档给出的模式是在commonMain声明expect工厂函数在各平台sourceSet提供actual实现从而让构造客户端的差异被隔离在平台边界内。// commonMain expect fun httpClient(config: HttpClientConfig*.() - Unit): HttpClient // androidMain actual fun httpClient(config: HttpClientConfig*.() - Unit): HttpClient HttpClient(OkHttp) { config(this) engine { addInterceptor(/* logging, auth */) } }共享侧的使用方式则完全平台无关——所有功能插件Feature在commonMain统一安装// Shared usage val client httpClient { install(ContentNegotiation) { json() } install(HttpTimeout) { requestTimeoutMillis 10_000 } defaultRequest { url(BuildKonfig.BASE_URL) header(HttpHeaders.ContentType, ContentType.Application.Json) } }几个值得展开的要点ContentNegotiation json()结合 kotlinx.serialization 完成 JSON 编解码配合Serializable的 DTO 使用DTO 放在data/remote目录HttpTimeoutrequestTimeoutMillis 10_000设定了 10 秒请求超时是生产应用的最低要求——详细指南 §5 强调所有网络调用必须包裹超时与重试策略defaultRequest统一注入 Base URL来自BuildKonfig.BASE_URL与默认请求头避免每个调用点重复声明Android 引擎选择 OkHttp好处是可以直接复用 OkHttp 的拦截器生态日志、鉴权、MockWebServer 测试等。iOS 目标可相应换成Darwin引擎调用点代码无需改动BuildKonfig这是用构建配置生成器如 BuildKonfig 插件在编译期注入环境常量如BASE_URL的做法对应详细指南 §7 中用 buildConfig 管理环境常量的最佳实践——debug/staging/release 构建变体各自注入不同 Base URL避免把敏感地址写进源码。四、本地持久化SQLDelight 表定义与驱动 expect/actualSQLDelight 的特点是schema 用.sq文件声明查询用类型安全 SQL 编写编译期生成对应平台的数据库代码Android 上是 SQLiteiOS 上是原生 SQLite。参考文档展示了最典型的建表 两条查询写法-- ItemEntity.sq CREATE TABLE ItemEntity ( id TEXT NOT NULL PRIMARY KEY, title TEXT NOT NULL, updatedAt INTEGER NOT NULL DEFAULT 0 ); selectAll: SELECT * FROM ItemEntity ORDER BY updatedAt DESC; upsertItem: INSERT OR REPLACE INTO ItemEntity (id, title, updatedAt) VALUES (?, ?, ?);要点说明updatedAt INTEGER NOT NULL DEFAULT 0用毫秒时间戳System.currentTimeMillis()级别做更新时间标记配合selectAll的ORDER BY updatedAt DESC实现最近更新在前的排序upsertItem使用INSERT OR REPLACE天然实现幂等写入刷新数据时可以直接覆盖旧行不必先查后写查询语句命名selectAll、upsertItem会直接成为生成的ItemEntityQueries类上的方法名。数据库驱动的平台差异同样用expect/actual隔离// commonMain — Database driver expect/actual expect class DatabaseDriverFactory { fun createDriver(): SqlDriver } // androidMain actual class DatabaseDriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver AndroidSqliteDriver(AppDatabase.Schema, context, app.db) }Android 侧需要Context才能创建AndroidSqliteDriver这正是为什么驱动工厂必须做成平台类而业务代码只需面对SqlDriver抽象。iOS 侧则用NativeSqliteDriver(AppDatabase.Schema, app.db)实现同一个接口。数据库实例AppDatabase由编译生成的AppDatabase类提供.sq文件经 Gradle 插件处理后生成随后通过依赖注入组装。五、共享 Repository单一数据源编排有了远程源Ktor与本地源SQLDelightRepository 就是两者的协调者。参考文档给出的实现同时展示了响应式读取与命令式刷新两种形态// commonMain class ItemRepositoryImpl( private val remoteSource: ItemRemoteDataSource, private val localSource: ItemLocalDataSource, ) : ItemRepository { override fun observeItems(): FlowListItem localSource.observeAll().map { entities - entities.map { it.toDomain() } } override suspend fun refreshItems(): ResultUnit runCatching { val items remoteSource.fetchItems() localSource.upsertAll(items.map { it.toEntity() }) } }observeItems(): FlowListItem从本地数据库发出响应式数据流天然支持 UI 订阅与自动更新。SQLDelight 的查询本身就是Flow驱动的数据库变更会自动重发新数据UI 无需手动刷新refreshItems(): ResultUnit拉取远端并回写本地runCatching把异常包装进Result不向调用方抛出未捕获异常——这与详细指南 §5 的黄金法则绝不让异常静默传播到用户或直接崩溃一致toDomain()/toEntity()本地实体Entity与领域模型Domain Model之间的映射。数据层暴露实体领域层使用模型避免平台/存储细节泄漏到上层这是一个典型的cache-first 架构UI 始终先读本地可能短暂显示旧数据后台通过refreshItems()拉新刷新后数据库更新自动驱动 UI 更新。六、Android 端消费共享 FlowViewModel 与 UiState共享模块产出的Flow在 Android 端如何消费参考文档给出标准做法ViewModel 通过构造器注入来自共享模块的 UseCase再把 Flow 转换为 UI 状态HiltViewModel class HomeViewModel Inject constructor( private val observeItems: ObserveItemsUseCase, // from shared module private val refreshItems: RefreshItemsUseCase // from shared module ) : ViewModel() { val uiState observeItems() .map { HomeUiState.Success(it) as HomeUiState } .stateIn( scope viewModelScope, started SharingStarted.WhileSubscribed(5_000), initialValue HomeUiState.Loading ) }UseCase 来自共享模块ObserveItemsUseCase、RefreshItemsUseCase都在commonMain定义Android ViewModel 只是它们的调用者——业务逻辑因此可被测试、可被其他平台复用stateIn的SharingStarted.WhileSubscribed(5_000)当 UI 订阅时启动上游 Flow停止订阅 5 秒后才取消既避免了无订阅者时白跑数据库查询又防止了频繁进出的抖动recomposition 导致反复启停initialValue HomeUiState.Loading状态流建立时立即给出 LoadingUI 可零判空地渲染首帧HomeUiState应为sealed classLoading / Success / Error 分支对应详细指南 §2 中 MVI 的sealed class UiState模式。ViewModel 把领域结果映射成 UI 状态UI 层只渲染状态、不接触数据源。七、Koin 依赖注入共享模块与 Android 模块的装配参考文档使用Koin做跨平台 DIKoin 本身是 Kotlin 库可在 commonMain 使用这是它相对 Hilt 在 KMM 场景的最大优势。装配分为两层// commonMain — shared Koin modules val sharedModule module { single { DatabaseDriverFactory(get()) } single { AppDatabase(getDatabaseDriverFactory().createDriver()) } singleItemRepository { ItemRepositoryImpl(get(), get()) } factory { ObserveItemsUseCase(get()) } factory { RefreshItemsUseCase(get()) } } // androidApp — Android-specific module val androidModule module { singleContext { androidApplication() } viewModel { HomeViewModel(get(), get()) } }singlevsfactoryDatabaseDriverFactory、AppDatabase、ItemRepository全局单例single因为数据库与驱动必须复用同一实例UseCase 用factory每次新建保持无状态、便于测试替换get()自动解析依赖ItemRepositoryImpl(get(), get())自动注入ItemRemoteDataSource与ItemLocalDataSource它们也应在 sharedModule 中注册AppDatabase(getDatabaseDriverFactory().createDriver())通过类型限定取出驱动工厂androidModule与viewModel { }viewModelDSL 让 Koin 负责 ViewModel 的创建与作用域管理等效于 Hilt 的HiltViewModel注解。应用启动时在Application中一次性装载两层模块// Application class class MyApp : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(thisMyApp) modules(sharedModule, androidModule) } } }androidContext(thisMyApp)把Application上下文注册进 Koin之后androidModule中的singleContext { androidApplication() }才能解析出上下文供DatabaseDriverFactory使用。整个装配链是Context → DatabaseDriverFactory → AppDatabase → ItemRepository → UseCase → ViewModel。八、Gradle 关键配置shared/build.gradle.ktsKMM 的构建配置核心是kotlin { }多目标声明与sourceSets分平台依赖。参考文档给出了最小可用配置kotlin { androidTarget() // Add other targets as needed (jvm, iosArm64, etc.) sourceSets { commonMain.dependencies { implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.sqldelight.runtime) implementation(libs.koin.core) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) } androidMain.dependencies { implementation(libs.ktor.client.okhttp) implementation(libs.sqldelight.android.driver) implementation(libs.koin.android) } } }目标声明androidTarget()必选按需追加jvm、iosArm64、iosSimulatorArm64、js、wasmJs等目标参考文档注释 Add other targets as needed。目标越多需要提供的actual实现越多建议从 Android iOS 起步commonMain 依赖全部选择跨平台版本——Ktor core / content-negotiation / kotlinx-json 序列化、SQLDelight runtime、Koin core、coroutines core、kotlinx.serialization jsonandroidMain 依赖平台专属实现——OkHttp 引擎ktor-client-okhttp、Android SQLite 驱动sqldelight-android-driver、Koin 的 Android 扩展koin-android提供androidContext()、viewModel {}等能力使用version cataloglibs.versions.toml统一管理版本符合详细指南 §7只使用 Kotlin DSL Version Catalog 管依赖的规范SQLDelight 还需在插件块配置com.squareup.sqldelightGradle 插件以启用.sq编译。九、Compose Multiplatform需要共享 UI 时的扩展上述所有内容共享的都是逻辑UI 仍是平台原生的。当团队希望进一步共享 UIAndroid Desktop Web时Compose Multiplatform 登场。参考文档给出的模式是共享无状态 Composable平台壳注入状态与回调// commonMain — shared composable Composable fun HomeScreenContent( state: HomeUiState, onRetry: () - Unit ) { when (state) { is HomeUiState.Loading - CircularProgressIndicator() is HomeUiState.Success - ItemList(state.items) is HomeUiState.Error - ErrorView(state.message, onRetry) } } // androidApp — wraps with Android ViewModel Composable fun HomeScreen(viewModel: HomeViewModel koinViewModel()) { val state by viewModel.uiState.collectAsStateWithLifecycle() HomeScreenContent(state, onRetry viewModel::refresh) }设计要点共享层只收参数HomeScreenContent接收HomeUiState与onRetry回调不持有 ViewModel、不关心数据来源——这样它才能跨平台复用平台壳负责装配Android 端用koinViewModel()获取 ViewModel用collectAsStateWithLifecycle()AndroidX lifecycle 扩展收集状态流再调用共享 Composable状态分支完整Loading / Success / Error 三个分支全部渲染对应详细指南 §3 中每个屏幕都必须有加载、空、错误状态的要求具体选择共享 UI还是各平台原生 UI时对照详细指南决策矩阵追求像素级一致的自定义 UI 优先原生追求三端一致且接受 Compose 生态约束时选择共享 UI。十、落地 Checklist把参考骨架接进真实项目综合参考文档与详细指南把以上内容落地到真实 KMM 项目时的检查清单工程骨架按第二节结构创建sharedandroidApp 可选iosApp/desktopAppGradle 配置声明目标、配置 version catalog、启用 SQLDelight 插件参考第八节依赖分组网络层commonMain 写expect httpClientandroidMain/iosMain 写actual安装ContentNegotiation、HttpTimeout、defaultRequest用BuildKonfig注入环境地址本地库定义.sqschema 与查询实现DatabaseDriverFactory的expect/actualRepository 编排remote local 双源Flow提供响应式读取runCatching包裹刷新逻辑实体/领域模型映射DI 装配sharedModulesingle驱动/数据库/RepositoryfactoryUseCase androidModuleContext、ViewModelApplication 中startKoinUI 消费ViewModel 注入 UseCasestateIn收敛状态流sealed UiState 渲染三态测试Ktor 层用 OkHttpMockWebServerSQLDelight 用内存数据库Repository/UseCase/ViewModel 用 JUnit MockK Turbine 验证 Flow对应详细指南 §6 测试金字塔共享模块的 domain/data 层可跨平台跑单元测试这是 KMM 的额外红利。结语Kotlin Multiplatform 的价值不在于一套代码跑所有平台而在于把最复杂、最容易出错的业务逻辑网络、持久化、领域规则收敛到一处让各平台只需保留薄薄的 UI 层。本文基于 agentic-awesome-skills 仓库的 android-dev 技能参考文档从工程结构、Ktor 网络层、SQLDelight 持久化、共享 Repository、ViewModel 状态流、Koin DI 到 Gradle 配置与 Compose Multiplatform给出了一套可直接照抄改造的完整骨架。需要更系统的开发流程错误处理、测试、发布时可继续阅读同目录下的 detailed-guide.md若想横向对比 Flutter、React Native、Hybrid 等技术栈参考 SKILL.md 中的选型矩阵即可。【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,115 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表