从零到三端:用 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

相关推荐
9624565 小时前
从“一键创建 ECS”到完整恢复状态机:ACK 抢占式节点恢复 Android 工具升级复盘
android
LCD液晶屏8 小时前
京瓷液晶屏停产替代:杭州立煌科技工业 LCD 解决方案
android·科技
2501_932750269 小时前
Android 自定义 View 入门
android
事圆则缓12 小时前
Android 开启线程的几种方式:Thread、线程池、HandlerThread 与协程
android
半摆烂日常1 天前
低代码平台API对接实践:接口鉴权与数据同步的完整实现
android·低代码·rxjava
事圆则缓1 天前
volatile 使用场景全解析:它能保证什么,又解决不了什么
android
蒲公英内测分发1 天前
智能投影仪参加海外展会,未上架的 Android 配套 App 怎么让客户限时试用?
android
ii_best1 天前
按键精灵手机端开发安卓版实战:手写一个可最小化、可拖动的「运行日志悬浮窗」(附完整 源码 + 踩坑记录)
android·ios·智能手机·自动化·ai编程·按键精灵
终端安全笔记1 天前
安卓做 MDM 管控,先分清三种注册入口
android·安全·智能手机