从零到三端:用 Kotlin Multiplatform + Compose 构建跨平台财务追踪应用

从零到三端:用 Kotlin Multiplatform + Compose 构建跨平台财务追踪应用

让每一次增长,都有迹可循。

前言

作为一名 Kotlin 开发者,一直想尝试 Kotlin Multiplatform (KMP) + Compose Multiplatform 的全栈跨平台方案。最近完成了一个名为 Gainful(盈迹) 的个人财务追踪应用,支持 Android、iOS 和 Desktop 三端,今天分享一下整个开发过程中的思考和实践。

UI 设计 :使用 OpenDesign 辅助完成界面设计与原型验证。

为什么选择 KMP + Compose?

  • 一套 Kotlin 代码,三端共享
  • Compose 声明式 UI 开发效率高
  • 原生性能,不像 WebView 方案有性能损耗
  • 社区活跃,JetBrains 持续投入

效果展示

仪表盘

仪表盘页面展示当日盈亏、总盈亏、持仓走势图和关键指标,一目了然掌握投资概况。

持仓分析

持仓页面提供投资比重可视化(Treemap 图表)和持仓明细,支持实时行情显示。

交易记录

交易记录支持按类型筛选(全部/买入/卖出/股息),清晰展示每笔交易的详情。

设置

设置页面支持自定义涨跌颜色、交易时段配置、CSV 导入导出等功能。

技术栈选型

类别 技术 选择理由
网络请求 Ktor 官方 KMP 网络库,类型安全
本地存储 Room Google 官方跨平台 ORM
依赖注入 Koin 轻量级,KMP 支持好
导航 Navigation3 Compose 官方导航方案
UI 框架 Compose Multiplatform 声明式 UI,三端统一

架构设计

MVI 数据流

项目采用 MVI (Model-View-Intent) 架构,单向数据流驱动 UI 更新:

markdown 复制代码
User Action → Intent → ViewModel → State → UI
     ↑                                    │
     └────────────────────────────────────┘

这种架构的好处:

  • 状态可预测,便于调试
  • 单向数据流,逻辑清晰
  • 易于测试和维护

Clean Architecture 分层

参考 Google 的 Now in Android 项目,采用 Clean Architecture 分层:

bash 复制代码
shared/
├── commonMain/        # 平台无关代码
├── androidMain/       # Android 平台实现
├── iosMain/           # iOS 平台实现
└── jvmMain/           # Desktop 平台实现

core/
├── common/            # 通用工具
├── model/             # 数据模型
├── ui/                # 通用 UI 组件
├── data/              # Repository 接口
├── database/          # Room 数据库
├── domain/            # UseCase 业务逻辑
└── navigation/        # 导航配置

feature/
├── dashboard/         # 仪表盘
├── holdings/          # 持仓管理
├── transactions/      # 交易记录
└── settings/          # 设置

核心功能实现

1. 跨平台数据库(Room)

Room 在 KMP 中的使用体验非常好,几乎和 Android 原生一样:

kotlin 复制代码
// 定义 Entity
@Entity(tableName = "transactions")
data class TransactionEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val stockCode: String,
    val stockName: String,
    val type: TransactionType,
    val quantity: Int,
    val price: Double,
    val fee: Double,
    val timestamp: Long
)

// 定义 DAO
@Dao
interface TransactionDao {
    @Query("SELECT * FROM transactions ORDER BY timestamp DESC")
    fun getAllTransactions(): Flow<List<TransactionEntity>>
    
    @Insert
    suspend fun insertTransaction(transaction: TransactionEntity)
}

注意点:Room 在 KMP 中需要使用 BundledSQLiteDriver,数据库文件会打包到应用中。

2. 跨平台网络请求(Ktor)

Ktor 是 KMP 的标准网络解决方案:

kotlin 复制代码
// 定义 API 接口
interface StockApi {
    suspend fun searchAssets(keyword: String): List<AssetDto>
    suspend fun getStockPrice(code: String): StockPriceDto
}

// 实现
class StockApiImpl(private val client: HttpClient) : StockApi {
    override suspend fun searchAssets(keyword: String): List<AssetDto> {
        return client.get("api/assets/search") {
            parameter("keyword", keyword)
        }.body()
    }
}

3. 跨平台 UI(Compose)

Compose Multiplatform 实现了真正的三端统一 UI:

kotlin 复制代码
// 通用 Composable
@Composable
fun TransactionCard(transaction: Transaction) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceVariant
        )
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text(
                    text = transaction.stockName,
                    style = MaterialTheme.typography.titleMedium
                )
                Text(
                    text = transaction.type.displayName,
                    color = if (transaction.type == TransactionType.BUY) 
                        Color.Red else Color.Green
                )
            }
            Spacer(modifier = Modifier.height(8.dp))
            Text(text = "${transaction.quantity}股 × ${transaction.price}")
        }
    }
}

4. 国际化(i18n)

使用 Compose Resources 实现多语言支持:

kotlin 复制代码
// strings.xml (中文)
<string name="dashboard_title">仪表盘</string>
<string name="total_assets">总资产</string>

// strings-en.xml (英文)
<string name="dashboard_title">Dashboard</string>
<string name="total_assets">Total Assets</string>

// 使用
@Composable
fun DashboardScreen() {
    Text(text = stringResource(Res.string.dashboard_title))
}

开发中的坑与解决方案

1. 日期时间处理

问题java.time.* 在 commonMain 中不可用。

解决 :使用 kotlinx-datetime

kotlin 复制代码
// 错误写法
// val now = java.time.LocalDateTime.now()

// 正确写法
val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

2. iOS 框架配置

问题:iOS 需要静态框架,且需要配置 Team ID。

解决

kotlin 复制代码
// shared/build.gradle.kts
kotlin {
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "Shared"
            isStatic = true
        }
    }
}

3. Compose 资源 ID 生成

问题:多模块资源 ID 冲突。

解决:每个模块独立命名空间:

kotlin 复制代码
// 正确的导入方式
import gainful.feature.dashboard.generated.resources.Res
import gainful.feature.dashboard.generated.resources.dashboard_title

// 使用
stringResource(Res.string.dashboard_title)

开发体验总结

优点

  1. 开发效率高:一套代码三端运行,节省大量时间
  2. UI 一致性:Compose 保证了三端 UI 的一致性
  3. 类型安全:Kotlin 的类型系统让代码更健壮
  4. 热重载:Desktop 端支持 hot reload,开发体验好

需要注意

  1. 平台特定代码:仍需为各平台写少量平台特定代码
  2. 依赖版本:KMP 生态还在快速发展,依赖版本需要关注
  3. 调试工具:相比原生开发,调试工具还有提升空间

快速开始

Android

bash 复制代码
./gradlew :androidApp:assembleDebug

Desktop

bash 复制代码
./gradlew :desktopApp:run

iOS

  1. 打开 iosApp/iosApp.xcodeproj
  2. 配置 TEAM_ID
  3. 在 Xcode 中运行

项目地址

GitHub: github.com/Yoke0/Gainf...

总结

Kotlin Multiplatform + Compose Multiplatform 是一个非常有前途的跨平台方案。虽然目前还有一些坑需要踩,但整体开发体验已经非常不错。对于想要一套代码覆盖多端的开发者来说,这是一个值得尝试的技术栈。

如果你有任何问题,欢迎在评论区讨论!


标签:#Kotlin #KMP #ComposeMultiplatform #跨平台开发 #Android #iOS #Desktop

相关推荐
hunterandroid10 小时前
Android WebView JSBridge 治理实战:从线上白屏崩溃到协议化通信
android·前端
一个用户名i10 小时前
【Compose 系列】第 1 篇:认识 Compose,为什么要学它
android·android jetpack
极客猴子11 小时前
iPhone实时转写软件推荐:会议录音功能真实体验
android·人工智能·飞书
达令哥11 小时前
告别 ARouter!基于 Google 官方 Navigation 3 + KSP 打造 Compose 时代的双轨制路由框架
android·前端
YF021111 小时前
Android App启动与权限管控
android
嘟哩DuliDuli12 小时前
AI 短剧生成为什么要有角色库、场景库和镜头卡
android·人工智能·安全·ai·软件工程
NINO13 小时前
深度解析 Android ANR:从标准分析流程到 NativePollOnce 疑难破局
android
阿巴斯甜13 小时前
AppFunctions 完整介绍
android
蜡台14 小时前
Jetpack Compose 从入门到精通
android·前端·kotlin·compose·jepack
toooooop814 小时前
踩坑:PHP7.2导出Excel正常,升级7.3文件损坏需修复
android·excel