Kotlin Multiplatform Development Help

在iOS与Android之间共享更多逻辑

现在你已通过外部依赖实现了通用逻辑,可以开始添加更复杂的逻辑。网络请求和数据序列化是使用Kotlin跨平台技术最常见的应用场景 。学习如何在首个应用中实现这些功能,以便在完成本次入门教程后能在未来项目中运用。

更新后的应用将通过互联网从SpaceX API获取数据,并显示SpaceX火箭最后一次成功发射的日期。

添加更多依赖项

需要在项目中添加以下跨平台库:

  • kotlinx.coroutines ,用于通过协程实现异步代码,支持并发操作。

  • kotlinx.serialization ,用于将JSON响应反序列化为处理网络操作的实体类对象。

  • Ktor ,用于创建HTTP客户端从互联网检索数据。

kotlinx.coroutines

在共享模块的build.gradle.kts文件中添加依赖:

kotlin { // ... sourceSets { commonMain.dependencies { // ... implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") } } }

跨平台Gradle插件会自动为kotlinx.coroutines添加平台特定(iOS和Android)的依赖。

kotlinx.serialization

在共享模块的build.gradle.kts文件开头添加插件:

plugins { // ... kotlin("plugin.serialization") version "2.0.0" }

Ktor

在共享模块中添加核心依赖和平台引擎:

kotlin { // ... val ktorVersion = "3.1.1" sourceSets { commonMain.dependencies { // ... implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") } androidMain.dependencies { implementation("io.ktor:ktor-client-android:$ktorVersion") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:$ktorVersion") } } }

点击通知中的立即同步同步Gradle文件。

创建API请求

添加数据模型

shared/src/commonMain/kotlin创建RocketLaunch.kt文件:

@Serializable data class RocketLaunch ( @SerialName("flight_number") val flightNumber: Int, @SerialName("name") val missionName: String, @SerialName("date_utc") val launchDateUTC: String, @SerialName("success") val launchSuccess: Boolean?, )

连接HTTP客户端

  1. 创建RocketComponent类并配置HTTP客户端:

    class RocketComponent { private val httpClient = HttpClient { install(ContentNegotiation) { json(Json { prettyPrint = true isLenient = true ignoreUnknownKeys = true }) } } }
  2. 添加获取最近成功发射日期的函数:

    private suspend fun getDateOfLastSuccessfulLaunch(): String { val rockets: List<RocketLaunch> = httpClient.get("https://api.spacexdata.com/v4/launches").body() val lastSuccessLaunch = rockets.last { it.launchSuccess == true } val date = Instant.parse(lastSuccessLaunch.launchDateUTC) .toLocalDateTime(TimeZone.currentSystemDefault()) return "${date.month} ${date.dayOfMonth}, ${date.year}" }
  3. 添加发射信息生成函数:

    suspend fun launchPhrase(): String = try { "The last successful launch was on ${getDateOfLastSuccessfulLaunch()} 🚀" } catch (e: Exception) { println("Exception during getting the date of the last successful launch $e") "Error occurred" }

创建数据流

更新Greeting.kt返回Flow类型:

fun greet(): Flow<String> = flow { emit(if (Random.nextBoolean()) "Hi!" else "Hello!") delay(1.seconds) emit("Guess what this is! > ${platform.name.reversed()}") delay(1.seconds) emit(daysPhrase()) emit(rocketComponent.launchPhrase()) }

添加网络权限

composeApp/src/androidMain/AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.INTERNET"/>

更新原生UI

Android应用

  1. 添加ViewModel依赖:

    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
  2. 创建MainViewModel管理数据流:

    class MainViewModel : ViewModel() { private val _greetingList = MutableStateFlow<List<String>>(listOf()) val greetingList: StateFlow<List<String>> get() = _greetingList init { viewModelScope.launch { Greeting().greet().collect { phrase -> _greetingList.update { list -> list + phrase } } } } }
  3. 更新UI组件:

    @Composable fun App(mainViewModel: MainViewModel = viewModel()) { val greetings by mainViewModel.greetingList.collectAsStateWithLifecycle() Column { greetings.forEach { greeting -> Text(greeting) Divider() } } }

iOS应用

使用SKIE方案

  1. 添加SKIE插件:

    plugins { id("co.touchlab.skie") version "0.8.2" }
  2. 更新ViewModel:

    @MainActor class ViewModel: ObservableObject { @Published var greetings: [String] = [] func startObserving() async { for await phrase in Greeting().greet() { self.greetings.append(phrase) } } }

使用KMP-NativeCoroutines方案

  1. 添加插件依赖:

    plugins { id("com.google.devtools.ksp") id("com.rickclephas.kmp.nativecoroutines") }
  2. 标记Flow函数:

    @NativeCoroutines fun greet(): Flow<String> = flow { ... }
  3. 在Xcode中添加SPM依赖:

    import KMPNativeCoroutinesAsync @MainActor class ViewModel: ObservableObject { func startObserving() async { do { let sequence = asyncSequence(for: Greeting().greet()) for try await phrase in sequence { self.greetings.append(phrase) } } catch { print("Error: \(error)") } } }

下一步

在教程最后部分,你将完成项目并了解后续步骤。

进入下一部分

参考资源

获取帮助

22 四月 2025