在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客户端
创建
RocketComponent
类并配置HTTP客户端:class RocketComponent { private val httpClient = HttpClient { install(ContentNegotiation) { json(Json { prettyPrint = true isLenient = true ignoreUnknownKeys = true }) } } }添加获取最近成功发射日期的函数:
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}" }添加发射信息生成函数:
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应用
添加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")创建
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 } } } } }更新UI组件:
@Composable fun App(mainViewModel: MainViewModel = viewModel()) { val greetings by mainViewModel.greetingList.collectAsStateWithLifecycle() Column { greetings.forEach { greeting -> Text(greeting) Divider() } } }
iOS应用
使用SKIE方案
添加SKIE插件:
plugins { id("co.touchlab.skie") version "0.8.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方案
添加插件依赖:
plugins { id("com.google.devtools.ksp") id("com.rickclephas.kmp.nativecoroutines") }标记Flow函数:
@NativeCoroutines fun greet(): Flow<String> = flow { ... }在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