从 Android 组件化到 KMP 跨平台底座(二):Flutter 也是跨平台,为什么还要学习 KMP + CMP?
前言
学习 KMP 之前,我已经接触并学习过 Flutter。
Flutter 可以使用一套 Dart 代码开发 Android、iOS,甚至还可以扩展到 Web 和桌面端。
页面可以共享,业务逻辑也可以共享。
那么问题就来了:
Flutter 已经可以跨平台了,为什么还要继续学习 KMP?
进一步说:
Compose Multiplatform 也可以共享 UI,它和 Flutter 又有什么区别?
一开始很容易把这个问题理解成:
Flutter 和 KMP 到底谁更好?
但随着理解深入,会发现这个问题本身就不够准确。
因为 Flutter、KMP 和 CMP 虽然都与跨平台有关,但它们解决问题的角度并不完全相同。
更重要的是:
Flutter 和 KMP 不一定是竞争关系,它们也可以组合使用。
例如:
Flutter
负责跨平台 UI
KMP
负责跨平台业务底座
所以,这一篇不是简单比较 Flutter 和 KMP 谁更强,而是要讲清楚:
-
Flutter、KMP、CMP 分别解决什么问题
-
已经学了 Flutter,为什么 KMP 仍然值得学习
-
Flutter 和 KMP 是否只能二选一
-
KMP + Flutter 到底应该怎么设计
-
KMP + CMP 和 KMP + Flutter 有什么区别
-
Android 开发者应该如何选择跨平台路线
一、先分清 Flutter、KMP 和 CMP
在讨论技术选型之前,首先要把三个概念分清楚。
1. Flutter 是什么?
Flutter 是一套完整的跨平台应用开发框架。
开发者主要使用:
-
Dart
-
Flutter Widget
-
Flutter 路由
-
Flutter 状态管理
-
Flutter 插件体系
-
Flutter 渲染体系
来完成多个平台的应用开发。
一个典型的 Flutter 项目可能是:
flutter_app/
├── lib/
│ ├── core/
│ ├── data/
│ ├── domain/
│ ├── feature/
│ ├── router/
│ └── main.dart
│
├── android/
├── ios/
├── web/
├── macos/
└── windows/
其中主要的 UI 和业务逻辑都写在:
lib/
例如:
-
登录页面
-
设备列表
-
设备详情
-
网络请求
-
Repository
-
状态管理
-
路由
-
本地缓存
通常都可以使用 Dart 和 Flutter 实现。
因此,Flutter 更像是:
提供一套完整的跨平台应用开发体系。
2. KMP 是什么?
KMP 的全称是 Kotlin Multiplatform。
它允许开发者使用 Kotlin,在多个平台之间共享代码。
典型项目结构可能是:
sharedLogic/
├── commonMain/
├── androidMain/
├── iosMain/
└── jsMain/
其中:
commonMain
负责公共代码,例如:
-
数据模型
-
网络请求
-
Repository
-
UseCase
-
业务规则
-
数据解析
-
错误处理
-
表单校验
-
Token 管理
-
状态定义
平台差异代码则分别放在:
androidMain
iosMain
jsMain
例如:
androidMain
├── Android 日志实现
├── DataStore 存储实现
└── Android 平台信息
iosMain
├── iOS 日志实现
├── Keychain 存储实现
└── iOS 平台信息
KMP 本身并不强制所有平台使用同一套 UI。
因此,使用纯 KMP 时可以是:
Android UI:Compose 或 XML
iOS UI:SwiftUI 或 UIKit
Web UI:React、Vue 或其他 Web 框架
业务逻辑:KMP sharedLogic
所以,KMP 更像是:
建设一套可以被多个平台复用的 Kotlin 业务底座。
3. CMP 是什么?
CMP 的全称是 Compose Multiplatform。
它建立在 Kotlin Multiplatform 之上,进一步解决 Compose UI 的跨平台复用问题。
只有 KMP 时,可以是:
sharedLogic
负责共享业务逻辑
Android
使用 Compose
iOS
使用 SwiftUI
加入 CMP 后,可以变成:
sharedLogic
负责共享业务逻辑
sharedUI
负责共享 Compose UI
androidApp
iosApp
webApp
desktopApp
负责平台入口
因此,三者可以先这样理解:
Flutter
= Dart 业务逻辑 + Flutter UI
KMP
= Kotlin 跨平台业务逻辑
KMP + CMP
= Kotlin 跨平台业务逻辑 + Compose 跨平台 UI
但是,这个关系还不完整。
因为 Flutter 也可以只负责 UI,底层业务逻辑交给 KMP。
于是还存在第四种组合:
KMP + Flutter
= Kotlin 跨平台业务底座 + Flutter 跨平台 UI
二、Flutter 和 KMP 的目标并不完全相同
Flutter 和 KMP 都可以参与跨平台开发,但它们的出发点不同。
Flutter 更倾向于:
使用一套完整的跨平台框架开发整个应用。
KMP 更倾向于:
共享适合共享的 Kotlin 代码,同时保留不同平台自己的开发方式。
假设现在要开发一个设备巡检系统,包含:
-
登录
-
设备列表
-
设备详情
-
扫码识别设备
-
地图点位
-
巡检表单
-
Token 管理
-
网络请求
-
错误处理
如果使用纯 Flutter,整体可能是:
Flutter App
├── 登录页面
├── 设备列表页面
├── 设备详情页面
├── 巡检表单页面
├── 扫码页面
├── 地图页面
├── 网络请求
├── Repository
├── 状态管理
└── 本地存储
UI 和主要业务逻辑都由 Flutter 负责。
如果使用纯 KMP,整体可能是:
sharedLogic
├── 登录业务逻辑
├── 设备数据模型
├── DeviceRepository
├── 二维码结果解析
├── 地图点位模型
├── 巡检表单校验
├── 网络请求
└── Token 管理
androidApp
├── Compose
├── CameraX
├── ML Kit
└── 高德地图
iosApp
├── SwiftUI
├── AVFoundation
└── MapKit
两种方案都实现了跨平台,但是共享范围不同。
Flutter 通常会共享:
UI
+
页面交互
+
业务逻辑
+
数据层
纯 KMP 通常会共享:
业务逻辑
+
数据层
+
领域模型
UI 是否共享,可以另外决定。
三、Flutter 更像“统一开发整个应用”
Flutter 的主要特点,是 UI 和业务逻辑通常都运行在 Flutter 体系中。
例如设备模型可以使用 Dart 定义:
class Device {
final String id;
final String name;
final DeviceStatus status;
const Device({
required this.id,
required this.name,
required this.status,
});
}
页面使用 Flutter Widget:
class DeviceListPage extends StatelessWidget {
const DeviceListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('设备列表'),
),
body: const DeviceListView(),
);
}
}
网络请求、状态管理、路由也会进入 Flutter 技术体系:
Dart
Flutter Widget
Dio
Riverpod / Bloc / Provider
go_router
Flutter Plugin
MethodChannel
这种方式的优势很明显:
-
UI 复用率高
-
多个平台页面容易保持一致
-
一套代码可以维护多个客户端
-
适合从零开始的跨平台项目
-
普通列表、表单和内容页面开发效率较高
但它也意味着,项目的主要技术体系会逐渐转向 Flutter。
原有 Android 项目中的:
-
Compose 页面
-
XML 页面
-
Android ViewModel
-
Kotlin Repository
-
Android Library
-
Kotlin 业务模型
并不能直接变成 Flutter 代码。
它们要么继续留在原生层,要么重新使用 Dart 实现,要么通过桥接暴露给 Flutter。
四、KMP 更像“共享底座,平台分别开发”
KMP 不要求整个应用都使用同一套 UI。
它更关注:
哪些代码真正适合跨平台共享?
例如设备模型:
data class Device(
val id: String,
val name: String,
val status: DeviceStatus
)
enum class DeviceStatus {
ONLINE,
OFFLINE,
WARNING
}
二维码解析逻辑:
data class ScanResult(
val rawContent: String,
val deviceId: String?,
val type: String?
)
class ScanParser {
fun parse(rawContent: String): ScanResult {
val params = rawContent
.substringAfter("?", rawContent)
.split("&")
.mapNotNull {
val pair = it.split("=")
if (pair.size == 2) {
pair[0] to pair[1]
} else {
null
}
}
.toMap()
return ScanResult(
rawContent = rawContent,
deviceId = params["deviceId"],
type = params["type"]
)
}
}
巡检表单校验:
class InspectionValidator {
fun validate(form: InspectionForm): ValidationResult {
if (form.deviceId.isBlank()) {
return ValidationResult.Failure(
message = "设备编号不能为空"
)
}
if (form.items.isEmpty()) {
return ValidationResult.Failure(
message = "巡检项目不能为空"
)
}
return ValidationResult.Success
}
}
这些逻辑都不依赖:
-
Android Context
-
Activity
-
CameraX
-
高德地图
-
SwiftUI
-
UIKit
因此可以进入:
commonMain
而扫码、地图、相机、权限等能力继续留在平台侧:
Android
├── CameraX
├── ML Kit
├── 高德地图
└── Android 权限
iOS
├── AVFoundation
├── Vision
├── MapKit
└── iOS 权限
所以,KMP 更强调:
公共业务核心
│
├── Android 平台实现
├── iOS 平台实现
└── Web 平台实现
五、已经学习 Flutter,为什么还要学习 KMP?
对于 Android 开发者来说,Flutter 和 KMP 带来的能力并不完全重复。
学习 Flutter,可以获得:
-
完整跨平台 App 开发能力
-
Dart 语言能力
-
Flutter UI 开发能力
-
跨平台路由和状态管理能力
-
Flutter 插件开发能力
-
一套 UI 覆盖 Android 和 iOS 的能力
学习 KMP,则可以获得:
-
Kotlin 跨平台能力
-
Android 与 iOS 业务逻辑共享能力
-
原生项目渐进式改造能力
-
多平台业务底座设计能力
-
平台抽象和平台适配能力
-
KMP 多模块组件化能力
-
Kotlin 与 iOS 平台互操作能力
Flutter 更关注:
如何使用统一框架开发多个平台的应用?
KMP 更关注:
多平台项目中,
哪些代码应该共享,
哪些代码应该留在平台侧?
所以,学习 Flutter 后再学习 KMP,不是在重复学习跨平台,而是在补充另一种跨平台架构能力。
六、原因一:KMP 更适合渐进式接入现有 Android 项目
假设现在已经有一个运行多年的 Android 项目:
androidApp/
├── app
├── lib-network
├── lib-storage
├── lib-qrcode
├── lib-map
├── feature-auth
├── feature-device
└── feature-inspection
项目已经包含:
-
大量 Kotlin 业务代码
-
Compose 或 XML 页面
-
CameraX
-
高德地图
-
WebView
-
蓝牙
-
MQTT
-
Android Service
-
后台任务
如果要使用 Flutter,需要考虑:
-
哪些页面改成 Flutter
-
Flutter 和原生页面如何跳转
-
Kotlin 模型如何传递给 Dart
-
原有 Android Library 如何包装
-
Flutter 和原生生命周期如何协调
-
原生能力如何通过插件或者通道暴露
但如果引入 KMP,可以先新增:
sharedLogic
然后逐步迁移纯 Kotlin 代码。
第一阶段,迁移数据模型:
Device
DeviceStatus
GeoPoint
ScanResult
InspectionForm
第二阶段,迁移纯业务逻辑:
ScanParser
LoginValidator
InspectionValidator
DeviceStatusMapper
第三阶段,迁移公共架构能力:
ApiResult
AppError
Repository
UseCase
Network
TokenStorage
原来的 Android 页面和平台能力可以继续使用。
因此,KMP 更适合:
从已有 Android 项目中逐步提取跨平台业务底座。
它不是要求一次性重写整个应用,而是允许业务逻辑逐步下沉。
七、原因二:Android 开发者可以继续使用 Kotlin
Android 开发者学习 Flutter,需要增加一套新的技术体系:
Kotlin / Java
+
Dart / Flutter
这本身没有问题。
多掌握一种语言和框架可以拓宽技术能力。
但是在大型项目中,同时维护两套语言体系也意味着:
-
Kotlin 和 Dart 可能各有一套业务模型
-
Android 原生层和 Flutter 层需要通信
-
团队需要同时掌握两套生态
-
调试链路可能跨越多个运行层
-
公共逻辑需要决定到底放在哪一层
KMP 则可以让 Android 开发者继续使用熟悉的 Kotlin:
commonMain:Kotlin
androidMain:Kotlin
iosMain:Kotlin
Android App:Kotlin
CMP UI:Kotlin
Android 开发者已有的很多能力都可以继续复用:
-
Kotlin
-
协程
-
Flow
-
Compose
-
Repository
-
UseCase
-
Gradle
-
多模块组件化
-
依赖倒置
-
接口抽象
需要新增学习的主要是:
-
Source Set
-
Kotlin/Native
-
iOS 平台 API
-
expect 和 actual
-
跨平台依赖
-
Swift 与 Kotlin 互操作
-
KMP 模块边界
因此,KMP 更像是在 Kotlin 基础上扩大平台边界。
八、原因三:KMP 不强制放弃原生 UI
已有 Android 团队可能已经积累了大量 Compose 组件:
AppButton
AppDialog
AppTopBar
DeviceCard
StatusTag
InspectionForm
MapMarkerInfoWindow
iOS 团队也可能已经有成熟的 SwiftUI 组件库。
在这种情况下,团队未必希望把全部 UI 都换成同一套跨平台方案。
使用纯 KMP 时,可以采用:
Android UI:Compose
iOS UI:SwiftUI
Web UI:React 或 Vue
业务逻辑:KMP sharedLogic
这种方式可以保证:
-
Android 保留 Android 的平台体验
-
iOS 保留 iOS 的平台体验
-
平台团队继续使用熟悉的 UI 技术
-
公共业务规则只维护一份
-
强平台页面可以深入使用系统 API
例如地图页面。
Android 使用高德地图:
Compose / Android View
│
▼
高德地图 Android SDK
iOS 使用 MapKit:
SwiftUI / UIKit
│
▼
MapKit
但它们共用相同的点位模型:
data class GeoPoint(
val latitude: Double,
val longitude: Double
)
data class MapMarkerModel(
val id: String,
val title: String,
val point: GeoPoint
)
所以:
KMP 可以共享业务,但不强迫所有平台使用相同的 UI。
九、原因四:KMP 更适合建设跨平台业务底座
Flutter 项目同样可以分层和组件化,例如:
core
data
domain
feature
presentation
但是,从已有 Android 组件化项目继续演进时,KMP 更容易形成明确的跨平台业务底座:
core-model
core-result
core-network
core-storage
core-platform
core-qrcode
core-map
feature-auth
feature-device
feature-inspection
这些模块不仅可以服务于一个 App,还可以服务于多个终端:
Android App
iOS App
Flutter App
Compose Multiplatform App
Desktop App
其他 Kotlin 项目
例如:
core-model
负责公共业务模型。
core-network
负责网络请求、序列化和错误转换。
core-storage
负责 Token、用户信息和缓存能力。
core-qrcode
负责二维码解析规则,不负责相机。
core-map
负责经纬度模型、坐标转换和点位业务,不负责地图 UI。
公司后续如果存在:
设备巡检 App
维保 App
物业管理 App
管理人员 App
桌面管理工具
它们都可以依赖同一套 KMP Core。
这时,KMP 的定位已经不只是“帮助一个 App 跨平台”,而是:
建设一套可以被不同客户端复用的业务核心。
十、Flutter 和 KMP 并不一定二选一
前面主要讲了纯 Flutter 和纯 KMP。
但实际上,还有一种很有价值的架构:
KMP + Flutter
也就是:
Flutter
负责跨平台 UI
KMP
负责跨平台业务逻辑
整体结构可以设计成:
Flutter
├── 页面
├── Widget
├── 路由
├── 主题
├── 动画
└── 页面状态适配
│
▼
Flutter Plugin / Platform Channel
│
▼
KMP sharedLogic
├── model
├── result
├── network
├── repository
├── usecase
├── validation
├── error
└── storage
│
▼
androidMain / iosMain
├── Android 平台实现
└── iOS 平台实现
这种模式下:
Flutter
不负责核心业务规则
KMP
不负责 Flutter UI
两者各自负责自己更擅长的部分。
十一、KMP + Flutter 中,Flutter 只负责什么?
如果把 Flutter 定位为 UI 层,那么 Flutter 主要负责:
-
页面
-
Widget
-
路由
-
动画
-
主题
-
用户交互
-
加载状态展示
-
错误状态展示
-
页面状态适配
-
调用 KMP UseCase
例如设备列表页面:
Flutter DeviceListPage
│
▼
调用 GetDeviceListUseCase
│
▼
KMP Repository
│
▼
KMP Network
│
▼
返回设备列表
│
▼
Flutter 渲染页面
Flutter 层不需要重新实现:
-
网络错误转换
-
设备状态判断
-
Token 刷新
-
二维码内容解析
-
巡检表单业务校验
-
DTO 到领域模型转换
这些逻辑都可以由 KMP 负责。
十二、KMP + Flutter 中,KMP 负责什么?
KMP 可以负责:
model
result
network
repository
usecase
validator
parser
token
error
storage abstraction
例如:
sealed class ApiResult<out T> {
data class Success<T>(
val data: T
) : ApiResult<T>()
data class Failure(
val code: String,
val message: String
) : ApiResult<Nothing>()
}
Repository:
interface DeviceRepository {
suspend fun getDeviceList(): ApiResult<List<Device>>
}
UseCase:
class GetDeviceListUseCase(
private val repository: DeviceRepository
) {
suspend fun execute(): ApiResult<List<Device>> {
return repository.getDeviceList()
}
}
Flutter 不需要关心底层具体使用:
-
Ktor
-
数据库
-
Android DataStore
-
iOS Keychain
-
Android 网络实现
-
iOS 平台实现
Flutter 只需要获得最终业务结果。
十三、Flutter 能直接调用 commonMain 吗?
概念上,Flutter 可以使用 KMP 的能力。
但是在工程上,Dart 通常不能直接:
import commonMain
Flutter 和 KMP 之间通常需要一层桥接。
Android 侧可能是:
Dart
│
▼
Flutter Plugin / MethodChannel
│
▼
Kotlin Android Bridge
│
▼
KMP sharedLogic
iOS 侧可能是:
Dart
│
▼
Flutter Plugin / MethodChannel
│
▼
Swift Bridge
│
▼
KMP Framework
也就是说:
Flutter 调用的是 Android 或 iOS 宿主层暴露出来的接口,宿主层再调用 KMP 业务底座。
因此,KMP + Flutter 虽然可行,但不是把两个工程目录简单放到一起就结束了。
还需要设计:
-
Dart 和 Kotlin 的模型转换
-
异步方法转换
-
异常转换
-
Flow 与 Stream 的适配
-
生命周期管理
-
Flutter 插件接口
-
Android 和 iOS 桥接层
十四、“Flutter 只负责 UI”并不代表 Flutter 零逻辑
从架构定位上,可以说:
Flutter 只负责 UI
但是从工程实现上,Flutter 层通常仍然需要少量展示层逻辑。
例如:
-
点击事件转发
-
页面跳转
-
路由参数
-
加载状态
-
错误提示
-
Dialog 显示
-
Toast 显示
-
页面级状态组合
-
KMP 模型向 Dart 展示模型转换
因此,更严谨的说法应该是:
Flutter
= UI + Presentation Adapter
KMP
= Domain + Data + Core Business
平台层
= SDK + 系统能力 + 硬件能力
不建议 Flutter 和 KMP 各维护一套完整业务状态。
例如不要同时存在:
KMP DeviceViewModel
Flutter DeviceBloc
两边各有一套业务规则
否则很容易出现:
-
状态重复
-
业务逻辑重复
-
数据转换复杂
-
问题难以定位
十五、KMP 和 Flutter 的状态应该由谁管理?
主要可以有两种方案。
方案一:KMP 暴露 UseCase,Flutter 管理页面状态
结构是:
KMP UseCase
│
▼
返回业务结果
│
▼
Flutter Riverpod / Bloc
│
▼
Flutter Widget
例如:
KMP
负责获取设备列表
Flutter
负责 Loading、Success、Error 页面状态
优点:
-
桥接比较简单
-
Flutter 页面开发自然
-
不需要复杂转换 Flow
-
适合第一阶段落地
缺点:
-
页面状态无法完全共享
-
Flutter 仍然需要维护展示层状态
对于入门项目,这种方式更容易理解和落地。
方案二:KMP 暴露完整页面状态
结构是:
KMP ViewModel
│
▼
StateFlow
│
▼
Flutter Stream Adapter
│
▼
Flutter Widget
KMP 可能定义:
sealed interface DeviceUiState {
data object Loading : DeviceUiState
data class Success(
val devices: List<Device>
) : DeviceUiState
data class Error(
val message: String
) : DeviceUiState
}
优点:
-
页面状态也可以在多个平台共享
-
Android、iOS、Flutter 可以使用同一套状态逻辑
-
业务状态集中
缺点:
-
Flow 和 Stream 需要适配
-
生命周期处理更复杂
-
桥接层更重
-
调试链路更长
对于这个系列的 Demo,更适合先采用:
KMP 提供 Repository 和 UseCase,Flutter 负责页面展示状态。
等业务底座稳定后,再考虑是否共享 ViewModel 和完整 UI State。
十六、扫码功能在 KMP + Flutter 中怎么设计?
以扫码识别设备为例。
扫码功能可以拆成两部分。
第一部分是平台能力:
相机预览
二维码识别
权限申请
生命周期
第二部分是公共业务逻辑:
二维码内容解析
设备编号提取
二维码类型判断
扫码结果校验
整体流程可以是:
Flutter 扫码页面
│
▼
调用 Flutter 扫码插件
│
├── Android:CameraX + ML Kit
│
└── iOS:AVFoundation
│
▼
获得 rawContent
│
▼
调用 KMP ScanParser
│
▼
返回 ScanResult
│
▼
Flutter 展示设备信息
KMP 中负责:
data class ScanResult(
val rawContent: String,
val deviceId: String?,
val type: String?
)
class ScanParser {
fun parse(rawContent: String): ScanResult {
// 公共二维码解析规则
TODO()
}
}
平台侧负责:
如何打开相机
如何申请权限
如何识别二维码
Flutter 负责:
扫码页面
扫描框动画
结果展示
跳转设备详情
这就是比较清晰的职责边界。
十七、地图功能在 KMP + Flutter 中怎么设计?
地图同样需要拆分。
KMP 可以共享:
data class GeoPoint(
val latitude: Double,
val longitude: Double
)
data class MapMarkerModel(
val id: String,
val title: String,
val point: GeoPoint
)
还可以共享:
-
点位数据转换
-
坐标业务校验
-
设备与点位关联规则
-
地图业务范围计算
-
Marker 业务模型
Flutter 或平台侧负责:
-
地图组件
-
Marker 绘制
-
地图缩放
-
定位权限
-
地图生命周期
-
高德地图或 MapKit SDK
整体可以是:
KMP
返回 MapMarkerModel 列表
│
▼
Flutter
展示地图和 Marker
│
▼
Flutter 地图插件
│
├── Android 地图 SDK
└── iOS 地图 SDK
这里也可以根据项目实际情况决定:
-
普通地图页面交给 Flutter 插件
-
强平台地图能力直接使用原生页面
-
KMP 只共享地图业务模型
十八、KMP + Flutter 的优势
1. Flutter 保留高 UI 复用率
Android 和 iOS 可以共享:
-
页面
-
Widget
-
路由
-
主题
-
动画
-
普通交互
2. 核心业务逻辑继续使用 Kotlin
已有 Android Kotlin 代码更容易迁入:
commonMain
例如:
-
接口结果封装
-
设备模型
-
二维码解析
-
Repository
-
UseCase
-
Token 管理
-
表单校验
3. 业务底座不与 Flutter 强绑定
KMP Core 不仅可以服务 Flutter,还可以提供给:
Android 原生 App
iOS 原生 App
Compose Multiplatform App
桌面端
其他 Kotlin 项目
Flutter 只是 KMP Core 的一个 UI 宿主。
4. 可以复用已有 Android 资产
原有的 Kotlin 业务逻辑不一定需要全部重写成 Dart。
只要代码可以脱离 Android SDK,就有机会迁入 KMP。
十九、KMP + Flutter 的代价
这套组合也不是没有成本。
项目中会同时存在:
Kotlin
Dart
Swift
Gradle
Xcode
Flutter Plugin
KMP Framework
需要额外处理:
-
Dart 与 Kotlin 模型转换
-
协程与 Future 转换
-
Flow 与 Stream 转换
-
KMP 异常向 Flutter 错误转换
-
Android 插件实现
-
iOS 插件实现
-
Framework 打包
-
多层调用链调试
-
多套依赖版本管理
例如一次设备列表请求,调用链可能是:
Flutter Widget
│
▼
Flutter Repository Adapter
│
▼
MethodChannel
│
▼
Android / iOS Bridge
│
▼
KMP UseCase
│
▼
KMP Repository
│
▼
KMP Network
调用链越长,调试成本越高。
因此,这种组合更适合:
-
已经有大量 Kotlin 业务代码
-
团队确实需要 Flutter 共享 UI
-
KMP Core 还要服务其他客户端
-
业务底座有长期复用价值
-
团队能够承担桥接层维护成本
如果只是开发一个普通 Flutter App,也没有现成 Kotlin 资产,那么业务逻辑全部使用 Dart 实现,通常更加简单。
二十、Flutter 和 CMP 都能共享 UI,有什么区别?
Flutter 和 CMP 都可以共享 UI,但它们属于不同的技术体系。
Flutter 使用:
Dart
Flutter Widget
Flutter 状态管理
Flutter 插件生态
CMP 使用:
Kotlin
Compose
协程
Flow
KMP 依赖体系
CMP 页面可能是:
@Composable
fun DeviceCard(
device: Device,
onClick: () -> Unit
) {
Card(
onClick = onClick
) {
Column {
Text(text = device.name)
Text(text = device.status.name)
}
}
}
Flutter 页面可能是:
class DeviceCard extends StatelessWidget {
final Device device;
final VoidCallback onTap;
const DeviceCard({
required this.device,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(device.name),
subtitle: Text(device.status),
onTap: onTap,
),
);
}
}
对于 Android Compose 开发者:
Compose
│
▼
Compose Multiplatform
是一条比较自然的技术延伸路线。
而 Flutter 则意味着:
Android 原生技术栈
│
▼
Dart + Flutter 技术栈
因此:
KMP + CMP
= Kotlin 业务逻辑 + Compose UI
KMP + Flutter
= Kotlin 业务逻辑 + Flutter UI
两者都可以共享业务和 UI,只是 UI 技术不同。
二十一、四种常见跨平台路线
综合来看,实际项目中至少存在四种跨平台路线。
路线一:纯 Flutter
Flutter App
├── UI
├── State
├── Repository
├── Network
├── Storage
└── Platform Plugin
Flutter 同时负责 UI 和主要业务逻辑。
适合:
-
从零开始的新项目
-
页面型应用
-
Android 和 iOS UI 高度一致
-
平台专属能力较少
-
团队已经掌握 Flutter
-
希望使用一套 Dart 代码维护多个平台
路线二:纯 KMP
sharedLogic
├── model
├── network
├── repository
└── usecase
androidApp
└── Compose / XML
iosApp
└── SwiftUI / UIKit
KMP 共享业务逻辑,UI 使用平台原生方案。
适合:
-
已有 Android 和 iOS 原生项目
-
希望渐进式共享业务逻辑
-
希望保留原生 UI
-
平台差异较大
-
原生能力占比较高
-
Android 和 iOS 团队独立维护页面
路线三:KMP + CMP
sharedLogic
└── 共享业务逻辑
sharedUI
└── Compose Multiplatform UI
androidApp
iosApp
webApp
desktopApp
Kotlin 负责业务逻辑,Compose 负责共享 UI。
适合:
-
团队以 Kotlin 开发者为主
-
已经掌握 Jetpack Compose
-
希望共享业务逻辑和普通 UI
-
希望建立完整 Kotlin 跨平台体系
-
可以接受部分强平台页面单独实现
路线四:KMP + Flutter
Flutter
├── 页面
├── Widget
├── 路由
├── 主题
└── Presentation Adapter
│
▼
Flutter Plugin / Platform Channel
│
▼
KMP sharedLogic
├── model
├── result
├── network
├── repository
├── usecase
├── validation
└── storage
Flutter 负责 UI,KMP 负责核心业务底座。
适合:
-
已有大量 Kotlin 业务代码
-
团队希望使用 Flutter 共享 UI
-
KMP Core 还需要服务原生 App
-
希望把 UI 框架与业务核心分离
-
团队能够维护 Flutter 与 KMP 的桥接层
二十二、设备巡检 Demo 应该选择哪种路线?
这个系列后续会统一围绕“设备巡检 KMP Demo”展开。
Demo 包含:
-
登录
-
设备列表
-
设备详情
-
扫码识别设备
-
地图点位
-
巡检表单
-
网络请求
-
错误处理
-
平台日志
-
Token 存储
这个 Demo 的核心目标是讲清楚:
从 Android 组件化
走向 KMP 跨平台业务底座
因此,系列主线仍然会采用:
KMP Core
+
部分 CMP UI
+
强平台能力平台化
具体来说:
sharedLogic
负责:
Device
DeviceStatus
ScanResult
GeoPoint
InspectionForm
ApiResult
AppError
LoginRepository
DeviceRepository
LoginUseCase
GetDeviceListUseCase
SubmitInspectionUseCase
ScanParser
InspectionValidator
TokenStorage
sharedUI
可以尝试负责:
登录页
设备列表页
设备详情页
巡检表单页
通用加载状态
通用错误页面
普通弹窗
Android 平台侧
负责:
CameraX
ML Kit
高德地图
Android 权限
Android Service
WebView
蓝牙
系统能力
iOS 平台侧
负责:
AVFoundation
MapKit
iOS 权限
Keychain
iOS 系统能力
但是,如果后续需要使用 Flutter 作为 UI,这套 sharedLogic 同样可以继续复用。
也就是说:
当前 Demo UI 可以使用 CMP
以后也可以换成 Flutter UI
底层 KMP Core 不需要全部推倒重写
这正是建设独立业务底座的价值。
二十三、Flutter、KMP 和 CMP 到底是什么关系?
现在可以得到一个更完整的关系。
Flutter
既可以独立完成 UI 和业务逻辑
也可以只负责 UI,
调用 KMP 业务底座
KMP
可以只共享业务逻辑,
平台分别开发 UI
也可以配合 CMP 共享 UI
还可以配合 Flutter 共享 UI
所以,它们并不是简单的替代关系。
完整关系可以表示为:
纯 Flutter
= Flutter UI + Dart 业务逻辑
纯 KMP
= 原生 UI + Kotlin 业务逻辑
KMP + CMP
= Compose UI + Kotlin 业务逻辑
KMP + Flutter
= Flutter UI + Kotlin 业务逻辑
真正需要选择的不是某个技术名字,而是:
UI 放在哪里?
业务逻辑放在哪里?
平台能力放在哪里?
团队愿意承担多少桥接成本?
二十四、到底应该怎么选择?
更适合纯 Flutter
如果项目符合以下特点,可以优先考虑纯 Flutter:
-
从零开始
-
页面型业务较多
-
Android 和 iOS 页面高度统一
-
平台能力较少
-
团队已经熟悉 Dart
-
希望快速完成多端交付
更适合纯 KMP
如果项目符合以下特点,可以优先考虑纯 KMP:
-
已有原生 Android 和 iOS 项目
-
希望逐步共享业务逻辑
-
不希望重写现有 UI
-
平台差异明显
-
原生能力占比较高
-
希望建设跨平台 SDK
更适合 KMP + CMP
如果项目符合以下特点,可以考虑 KMP + CMP:
-
团队主要使用 Kotlin
-
已经掌握 Compose
-
希望共享业务和 UI
-
普通页面占比较高
-
可以接受强平台能力单独实现
-
希望建立 Kotlin 跨平台应用体系
更适合 KMP + Flutter
如果项目符合以下特点,可以考虑 KMP + Flutter:
-
已经拥有大量 Kotlin 业务代码
-
已经建设或者准备建设 KMP Core
-
UI 团队更熟悉 Flutter
-
希望 Flutter 负责跨平台页面
-
KMP Core 还需要提供给其他客户端
-
能够承担桥接和模型转换成本
二十五、对 Android 开发者的学习建议
如果已经学习 Flutter,再进入 KMP,不需要把它们放在一起竞争。
更合理的理解是:
Flutter
让我拥有完整跨平台 App 开发能力
KMP
让我拥有跨平台业务底座设计能力
CMP
让我把 Compose UI 扩展到多个平台
学习顺序可以是:
第一阶段:KMP 基础
├── commonMain
├── androidMain
├── iosMain
├── expect / actual
└── 平台依赖
第二阶段:sharedLogic
├── model
├── result
├── network
├── storage
├── repository
├── usecase
└── error
第三阶段:KMP 多模块组件化
├── core-model
├── core-network
├── core-storage
├── feature-auth
└── feature-device
第四阶段:CMP
├── 共享组件
├── 登录页
├── 设备列表
├── 表单页面
└── 平台 UI 混合
第五阶段:其他 UI 宿主
├── Android 原生 UI
├── iOS 原生 UI
├── Compose Multiplatform
└── Flutter UI
不要一开始就追求:
Android、iOS、Web 所有代码百分之百共享。
更重要的是建立稳定的业务边界。
二十六、总结
Flutter、KMP 和 CMP 都可以参与跨平台开发,但它们采用了不同的技术路线。
Flutter 更像:
使用 Dart 和 Flutter 构建完整的跨平台应用。
KMP 更像:
使用 Kotlin 建设可以被多个平台复用的业务底座。
CMP 则是在 KMP 基础上进一步解决:
Compose UI 如何在多个平台之间共享。
已经学习 Flutter 后继续学习 KMP,并不是重复投入。
因为:
Flutter
= 跨平台应用开发能力
KMP
= 跨平台业务底座设计能力
CMP
= Compose 跨平台 UI 能力
更重要的是,Flutter 和 KMP 也不是只能二选一。
它们可以组合成:
KMP + Flutter
Flutter
负责 UI 和少量展示层逻辑
KMP
负责模型、网络、Repository、UseCase 和业务规则
Android / iOS
负责系统 SDK 和平台能力
因此,更完整的结论应该是:
Flutter
既可以独立负责 UI 和业务逻辑,
也可以只作为 KMP 之上的跨平台 UI 层。
KMP
既可以配合原生 UI,
也可以配合 CMP,
还可以配合 Flutter。
真正的技术选型,不是判断 Flutter 和 KMP 谁能淘汰谁,而是根据项目回答三个问题:
业务核心放在哪里?
UI 放在哪里?
平台差异放在哪里?
对于设备巡检这类项目,我目前更倾向于:
KMP Core
负责公共业务底座
CMP 或 Flutter
负责可共享的普通 UI
地图、扫码、定位、蓝牙等强平台能力
继续留在平台侧
可以用一句话概括:
Flutter 更擅长统一跨平台 UI,KMP 更适合沉淀跨平台业务底座;两者不仅可以选择其一,也可以组合使用。
下一篇预告
下一篇将正式进入 KMP 项目结构:
《KMP 项目结构一次讲清:commonMain、androidMain、iosMain 到底放什么?》
我们会重点讲清楚:
-
KMP 项目中的 Source Set 到底是什么
-
commonMain到底可以写什么 -
androidMain应该放哪些 Android 平台实现 -
iosMain为什么可以直接调用 iOS API -
jsMain和wasmJsMain分别是什么 -
expect和actual到底解决什么问题 -
sharedLogic、sharedUI、androidApp、iosApp应该如何组织 -
Android 开发者应该如何从 Module 思维转向 Source Set 思维
更多推荐




所有评论(0)