假设要做一个读书 App:Android 和 iOS 的界面可以各自写,但「读了 6 页,一共 10 页,进度是 60%」这条规则可以共用。Kotlin Multiplatform(KMP)让我们把这样的代码写进同一个 Kotlin 模块,再编译给两个平台使用。
代码该放在哪儿?
在一个同时面向 Android 和 iOS 的共享模块里,常见的目录是:
| 目录 | 放什么 |
|---|---|
commonMain | 两端共用的数据模型、业务规则和计算 |
androidMain | 需要调用 Android API 的代码 |
iosMain | 多个 iOS 目标共用的平台代码 |
commonTest | 可以在各目标上运行的共享测试 |
可以先找一段没有系统依赖、输入和输出都很明确的逻辑。阅读进度就很合适。
动手写一个共享函数
把下面的函数放到 shared/src/commonMain/kotlin/blog/kmp/ReadingProgress.kt:
package blog.kmp
fun readingProgress(readPages: Int, totalPages: Int): Int {
if (totalPages <= 0) return 0
val pages = readPages.coerceIn(0, totalPages)
return (pages.toLong() * 100 / totalPages).toInt()
}
readingProgress(6, 10) 返回 60。如果误传了负数或超过总页数,结果也会保持在 0% 到 100% 之间。这里取整数百分比,先转成 Long 可以避免乘以 100 时发生 Int 溢出。
接着在 commonTest 用 kotlin.test 写一组例子:
package blog.kmp
import kotlin.test.Test
import kotlin.test.assertEquals
class ReadingProgressTest {
@Test
fun handlesNormalAndOutOfRangeValues() {
assertEquals(60, readingProgress(6, 10))
assertEquals(0, readingProgress(-2, 10))
assertEquals(100, readingProgress(12, 10))
assertEquals(0, readingProgress(3, 0))
}
}
如果模板还没配置测试依赖,可以在共享模块的 kotlin { sourceSets { ... } } 里加上 commonTest.dependencies { implementation(kotlin("test")) },然后在 IDE 里用项目已有的目标运行测试。
两端做法不同时怎么办?
比如调试页想在共享代码里显示当前平台,可以用 expect 声明需要的函数,再用 actual 为每个平台提供实现。把下面三段分别放在对应目录,文件里的包名保持一致:
// commonMain/kotlin/blog/kmp/Platform.kt
package blog.kmp
expect fun platformName(): String
// androidMain/kotlin/blog/kmp/Platform.kt
package blog.kmp
actual fun platformName(): String = "Android"
// iosMain/kotlin/blog/kmp/Platform.kt
package blog.kmp
actual fun platformName(): String = "iOS"
下一步可以给读书 App 加一条「读完才标记完成」的规则,把它写在 commonMain,再用两端相同的测试数据跑一遍。从小函数开始,比一上来搬整个应用更容易看清共享代码的边界。
延伸阅读:KMP 项目结构、共享测试依赖、expect 与 actual。