Kotlin Multiplatform Development Help

使用Ktor和SQLDelight创建跨平台应用

本教程演示如何利用Android Studio,通过Kotlin Multiplatform为iOS和Android开发一款功能丰富的移动应用。该应用将实现以下功能:

  • 使用Ktor从公共SpaceX API获取数据

  • 通过SQLDelight将数据保存至本地数据库

  • 展示SpaceX火箭发射列表,包括发射日期、结果及详细描述

应用包含一个为iOS和Android平台共享代码的模块。业务逻辑与数据访问层仅在共享模块中实现一次,而两个平台的UI均采用原生实现。

模拟器与仿真器

项目中使用的跨平台库包括:

创建项目

  1. 配置跨平台开发环境。 检查必要工具列表并按需更新至最新版本

  2. 打开Kotlin跨平台向导

  3. New project选项卡确保勾选AndroidiOS选项

  4. 对于iOS选择Do not share UI选项,后续将为两个平台实现原生UI

  5. 点击Download按钮并解压下载的压缩包

    Kotlin跨平台向导
  6. 启动Android Studio

  7. 在欢迎界面点击Open ,或通过编辑器菜单选择File | Open

  8. 导航至解压的项目文件夹后点击Open

    Android Studio将识别包含Gradle构建文件的文件夹,将其作为新项目打开并启动初始Gradle同步

  9. 默认视图针对Android开发优化。要查看更适合跨平台开发的项目完整文件结构,请将视图从Android切换至Project

    选择项目视图

添加Gradle依赖

要为共享模块添加跨平台库,需在build.gradle.kts文件对应源集的dependencies {}块中添加依赖指令(implementation)。 kotlinx.serialization和SQLDelight库还需额外配置。

gradle/libs.versions.toml版本目录中修改或添加以下依赖:

  1. [versions]块中检查AGP版本并添加其余配置:

    [versions] agp = "8.2.2" ... coroutinesVersion = "1.7.3" dateTimeVersion = "0.6.0" koin = "3.5.3" ktor = "3.1.1" sqlDelight = "2.0.1" lifecycleViewmodelCompose = "2.7.0" material3 = "1.2.0"

  2. [libraries]块添加库引用:

    [libraries] ... android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesVersion" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "dateTimeVersion" } ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" } runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref="material3" }

  3. [plugins]块指定必要Gradle插件:

    [plugins] ... kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
  4. 添加依赖后系统会提示重新同步项目。点击Sync Now同步Gradle文件:

    同步Gradle文件
  5. shared/build.gradle.kts文件起始处的plugins {}块添加:

    plugins { // ... alias(libs.plugins.kotlinxSerialization) alias(libs.plugins.sqldelight) }
  6. 在同一文件中引用所有必要依赖:

    kotlin { // ... sourceSets { commonMain.dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.runtime) implementation(libs.kotlinx.datetime) implementation(libs.koin.core) } androidMain.dependencies { implementation(libs.ktor.client.android) implementation(libs.android.driver) } iosMain.dependencies { implementation(libs.ktor.client.darwin) implementation(libs.native.driver) } } }
    • 公共源集需要各库的核心组件,以及Ktor的序列化功能来处理网络请求与响应

    • iOS和Android源集还需SQLDelight和Ktor平台驱动

  7. 添加依赖后再次点击Sync Now同步Gradle文件

完成Gradle同步后,项目配置即告完成,可以开始编写代码。

创建应用数据模型

教程应用将包含公开的SpaceXSDK类作为网络与缓存服务的门面。数据模型包含三个实体类,分别表示:

  • 发射基本信息

  • 任务徽章图片链接

  • 相关文章URL

创建必要的数据类:

  1. shared/src/commonMain/kotlin目录创建com/jetbrains/spacetutorial嵌套包文件夹

  2. com.jetbrains.spacetutorial目录下创建entity子目录及Entity.kt文件

  3. 声明基础实体的数据类:

    import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class RocketLaunch( @SerialName("flight_number") val flightNumber: Int, @SerialName("name") val missionName: String, @SerialName("date_utc") val launchDateUTC: String, @SerialName("details") val details: String?, @SerialName("success") val launchSuccess: Boolean?, @SerialName("links") val links: Links ) { var launchYear = launchDateUTC.toInstant().toLocalDateTime(TimeZone.UTC).year } @Serializable data class Links( @SerialName("patch") val patch: Patch?, @SerialName("article") val article: String? ) @Serializable data class Patch( @SerialName("small") val small: String?, @SerialName("large") val large: String? )

每个可序列化类必须标注@Serializable注解。除非显式指定序列化器, kotlinx.serialization插件会为@Serializable类自动生成默认序列化器。

@SerialName注解支持重命名字段,便于使用更易读的标识符访问数据类属性。

配置SQLDelight与实现缓存逻辑

配置SQLDelight

SQLDelight库支持从SQL查询生成类型安全的Kotlin数据库API。编译期间,生成器会验证SQL查询并将其转换为可在共享模块中使用的Kotlin代码。

SQLDelight依赖已包含在项目中。要配置该库,打开shared/build.gradle.kts文件并添加sqldelight {}块:

sqldelight { databases { create("AppDatabase") { packageName.set("com.jetbrains.spacetutorial.cache") } } }

packageName参数指定生成Kotlin源代码的包名。按提示同步Gradle项目文件。

生成数据库API

首先创建包含所有必要SQL查询的.sq文件。默认情况下SQLDelight插件在源集的sqldelight目录查找.sq文件:

  1. shared/src/commonMain目录创建sqldelight新目录

  2. sqldelight目录下创建com/jetbrains/spacetutorial/cache嵌套包目录

  3. cache目录创建与build.gradle.kts中同名的AppDatabase.sq文件

  4. 添加创建发射数据表的代码:

    import kotlin.Boolean; CREATE TABLE Launch ( flightNumber INTEGER NOT NULL, missionName TEXT NOT NULL, details TEXT, launchSuccess INTEGER AS Boolean DEFAULT NULL, launchDateUTC TEXT NOT NULL, patchUrlSmall TEXT, patchUrlLarge TEXT, articleUrl TEXT );
  5. 添加插入数据的insertLaunch函数:

    insertLaunch: INSERT INTO Launch(flightNumber, missionName, details, launchSuccess, launchDateUTC, patchUrlSmall, patchUrlLarge, articleUrl) VALUES(?, ?, ?, ?, ?, ?, ?, ?);
  6. 添加清空数据的removeAllLaunches函数:

    removeAllLaunches: DELETE FROM Launch;
  7. 声明检索数据的selectAllLaunchesInfo函数:

    selectAllLaunchesInfo: SELECT Launch.* FROM Launch;
  8. 运行以下命令生成对应的AppDatabase接口:

    ./gradlew generateCommonMainAppDatabaseInterface

    生成的Kotlin代码存储在shared/build/generated/sqldelight目录

创建平台特定数据库驱动工厂

要初始化AppDatabase接口,需要向其传递SqlDriver实例。SQLDelight提供多种平台特定的SQLite驱动实现,因此需要为每个平台单独创建这些实例。

虽然可以通过预期与实际声明实现,但本项目将使用Koin来体验Kotlin跨平台的依赖注入。

  1. shared/src/commonMain/kotlin目录创建com/jetbrains/spacetutorial/cache包路径

  2. cache目录创建DatabaseDriverFactory接口:

    package com.jetbrains.spacetutorial.cache import app.cash.sqldelight.db.SqlDriver interface DatabaseDriverFactory { fun createDriver(): SqlDriver }
  3. 为Android实现该接口:在shared/src/androidMain/kotlin目录创建com.jetbrains.spacetutorial.cache包及DatabaseDriverFactory.kt文件

  4. 在Android上通过AndroidSqliteDriver类实现驱动:

    package com.jetbrains.spacetutorial.cache import android.content.Context import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver class AndroidDatabaseDriverFactory(private val context: Context) : DatabaseDriverFactory { override fun createDriver(): SqlDriver { return AndroidSqliteDriver(AppDatabase.Schema, context, "launch.db") } }
  5. 对于iOS,在shared/src/iosMain/kotlin目录创建相同包结构及文件:

    package com.jetbrains.spacetutorial.cache import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.native.NativeSqliteDriver class IOSDatabaseDriverFactory : DatabaseDriverFactory { override fun createDriver(): SqlDriver { return NativeSqliteDriver(AppDatabase.Schema, "launch.db") } }

后续将在平台特定代码中实现这些驱动的实例。

实现缓存

现已添加平台数据库驱动工厂和AppDatabase接口来执行数据库操作。接下来创建包装AppDatabase接口并包含缓存逻辑的Database类。

  1. 在公共源集shared/src/commonMain/kotlincom.jetbrains.spacetutorial.cache目录创建Database

  2. 通过构造函数传递抽象的DatabaseDriverFactory实例:

    package com.jetbrains.spacetutorial.cache internal class Database(databaseDriverFactory: DatabaseDriverFactory) { private val database = AppDatabase(databaseDriverFactory.createDriver()) private val dbQuery = database.appDatabaseQueries }

    该类可见性设为internal,表示仅限跨平台模块内部访问

  3. Database类中实现数据处理操作。首先创建返回所有火箭发射列表的getAllLaunches函数:

    import com.jetbrains.spacetutorial.entity.Links import com.jetbrains.spacetutorial.entity.Patch import com.jetbrains.spacetutorial.entity.RocketLaunch internal class Database(databaseDriverFactory: DatabaseDriverFactory) { // ... internal fun getAllLaunches(): List<RocketLaunch> { return dbQuery.selectAllLaunchesInfo(::mapLaunchSelecting).executeAsList() } private fun mapLaunchSelecting( flightNumber: Long, missionName: String, details: String?, launchSuccess: Boolean?, launchDateUTC: String, patchUrlSmall: String?, patchUrlLarge: String?, articleUrl: String? ): RocketLaunch { return RocketLaunch( flightNumber = flightNumber.toInt(), missionName = missionName, details = details, launchDateUTC = launchDateUTC, launchSuccess = launchSuccess, links = Links( patch = Patch( small = patchUrlSmall, large = patchUrlLarge ), article = articleUrl ) ) } }

  4. 添加清空数据库并插入新数据的clearAndCreateLaunches函数:

    internal class Database(databaseDriverFactory: DatabaseDriverFactory) { // ... internal fun clearAndCreateLaunches(launches: List<RocketLaunch>) { dbQuery.transaction { dbQuery.removeAllLaunches() launches.forEach { launch -> dbQuery.insertLaunch( flightNumber = launch.flightNumber.toLong(), missionName = launch.missionName, details = launch.details, launchSuccess = launch.launchSuccess ?: false, launchDateUTC = launch.launchDateUTC, patchUrlSmall = launch.links.patch?.small, patchUrlLarge = launch.links.patch?.large, articleUrl = launch.links.article ) } } } }

实现API服务

通过SpaceX公共APIv5/launches端点获取发射列表数据。

创建连接应用的API类:

  1. 在公共源集shared/src/commonMain/kotlin创建com/jetbrains/spacetutorial/network目录

  2. network目录创建SpaceXApi类:

    package com.jetbrains.spacetutorial.network import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json class SpaceXApi { private val httpClient = HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true useAlternativeNames = false }) } } }
    • 该类执行网络请求并将JSON响应反序列化为com.jetbrains.spacetutorial.entity包中的实体

    • 使用Ktor内容协商插件处理GET请求结果

  3. 声明返回火箭发射列表的数据检索函数:

    class SpaceXApi { // ... suspend fun getAllLaunches(): List<RocketLaunch> { return httpClient.get("https://api.spacexdata.com/v5/launches").body() } }
    • getAllLaunches函数标记为suspend因为它调用了挂起函数HttpClient.get()

    • GET请求URL作为参数传递给get()函数

构建SDK

iOS和Android应用将通过共享模块与SpaceX API通信,该模块将提供公开的SpaceXSDK类。

  1. 在公共源集shared/src/commonMain/kotlincom.jetbrains.spacetutorial目录创建SpaceXSDK类 通过构造函数提供DatabaseDriverFactory实例:

    package com.jetbrains.spacetutorial import com.jetbrains.spacetutorial.cache.Database import com.jetbrains.spacetutorial.cache.DatabaseDriverFactory import com.jetbrains.spacetutorial.network.SpaceXApi class SpaceXSDK(databaseDriverFactory: DatabaseDriverFactory, val api: SpaceXApi) { private val database = Database(databaseDriverFactory) }

    将通过SpaceXSDK类构造函数在平台特定代码中注入正确的数据库驱动

  2. 添加使用数据库和API获取发射列表的getLaunches函数:

    import com.jetbrains.spacetutorial.entity.RocketLaunch class SpaceXSDK(databaseDriverFactory: DatabaseDriverFactory, val api: SpaceXApi) { // ... @Throws(Exception::class) suspend fun getLaunches(forceReload: Boolean): List<RocketLaunch> { val cachedLaunches = database.getAllLaunches() return if (cachedLaunches.isNotEmpty() && !forceReload) { cachedLaunches } else { api.getAllLaunches().also { database.clearAndCreateLaunches(it) } } } }
  • 根据forceReload值返回缓存数据或从网络加载并更新缓存

  • 客户端可通过forceReload标志加载最新发射信息,实现下拉刷新功能

  • 由于Kotlin异常是非受检的,而Swift只有受检错误,因此需要使用@Throws注解标记可能抛出的异常类

创建Android应用

Kotlin跨平台向导已处理初始Gradle配置,因此shared模块已连接到Android应用。

在实现UI和展示逻辑前,向composeApp/build.gradle.kts文件添加所有必要的UI依赖:

kotlin { // ... sourceSets { androidMain.dependencies { implementation(libs.androidx.compose.material3) implementation(libs.koin.androidx.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) } // ... } }

按提示同步Gradle项目文件

添加网络访问权限

Android应用需要相应权限才能访问网络。在composeApp/src/androidMain/AndroidManifest.xml文件中添加:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <!--...--> </manifest>

添加依赖注入

Koin依赖注入允许声明可在不同上下文中使用的模块。本项目将创建两个模块:一个用于Android应用,另一个用于iOS应用。

声明包含Android组件的Koin模块:

  1. composeApp/src/androidMain/kotlin目录的com.jetbrains.spacetutorial包中创建AppModule.kt文件 声明两个单例

    package com.jetbrains.spacetutorial import com.jetbrains.spacetutorial.cache.AndroidDatabaseDriverFactory import com.jetbrains.spacetutorial.network.SpaceXApi import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val appModule = module { single<SpaceXApi> { SpaceXApi() } single<SpaceXSDK> { SpaceXSDK( databaseDriverFactory = AndroidDatabaseDriverFactory( androidContext() ), api = get() ) } }

    SpaceXSDK类构造函数注入了平台特定的AndroidDatabaseDriverFactory

  2. 创建自定义Application类来启动Koin模块 在AppModule.kt旁创建Application.kt文件:

    package com.jetbrains.spacetutorial import android.app.Application import org.koin.android.ext.koin.androidContext import org.koin.core.context.GlobalContext.startKoin class MainApplication : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MainApplication) modules(appModule) } } }
  3. AndroidManifest.xml<application>标签中指定MainApplication类:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application ... android:name="com.jetbrains.spacetutorial.MainApplication"> ... </application> </manifest>

现在可以开始实现使用平台特定数据库驱动提供信息的UI

准备包含发射列表的视图模型

将使用Jetpack Compose和Material 3实现Android UI。首先创建使用SDK获取发射列表的视图模型,然后设置Material主题,最后编写组合函数整合所有内容。

  1. composeApp/src/androidMain源集的com.jetbrains.spacetutorial包中创建RocketLaunchViewModel.kt文件:

    package com.jetbrains.spacetutorial import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.jetbrains.spacetutorial.entity.RocketLaunch class RocketLaunchViewModel(private val sdk: SpaceXSDK) : ViewModel() { private val _state = mutableStateOf(RocketLaunchScreenState()) val state: State<RocketLaunchScreenState> = _state } data class RocketLaunchScreenState( val isLoading: Boolean = false, val launches: List<RocketLaunch> = emptyList() )

    RocketLaunchScreenState实例将存储从SDK接收的数据和请求的当前状态

  2. 添加在视图模型协程作用域中调用SDKgetLaunches函数的loadLaunches函数:

    import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch class RocketLaunchViewModel(private val sdk: SpaceXSDK) : ViewModel() { //... fun loadLaunches() { viewModelScope.launch { _state.value = _state.value.copy(isLoading = true, launches = emptyList()) try { val launches = sdk.getLaunches(forceReload = true) _state.value = _state.value.copy(isLoading = false, launches = launches) } catch (e: Exception) { _state.value = _state.value.copy(isLoading = false, launches = emptyList()) } } } }
  3. 在类init {}块中添加loadLaunches()调用以在创建RocketLaunchViewModel对象时立即请求API数据:

    class RocketLaunchViewModel(private val sdk: SpaceXSDK) : ViewModel() { // ... init { loadLaunches() } }
  4. AppModule.kt文件中将视图模型添加到Koin模块:

    import org.koin.androidx.viewmodel.dsl.viewModel val appModule = module { // ... viewModel { RocketLaunchViewModel(sdk = get()) } }

构建Material主题

App()组合函数将围绕Material Theme提供的AppTheme函数构建:

  1. 使用Material主题生成器生成Compose应用主题 完成后点击右上角Export并选择**Jetpack Compose (Theme.kt)**选项

  2. 解压后将theme文件夹复制到composeApp/src/androidMain/kotlin/com/jetbrains/spacetutorial目录

    主题目录位置
  3. 在每个主题文件Color.ktTheme.kt中修改包声明:

    package com.jetbrains.spacetutorial.theme
  4. Color.kt文件中添加表示成功与失败发射的颜色变量:

    val app_theme_successful = Color(0xff4BB543) val app_theme_unsuccessful = Color(0xffFC100D)

实现展示逻辑

创建应用的App()主组合函数,并从ComponentActivity类调用:

  1. com.jetbrains.spacetutorial包的theme目录旁创建App.kt文件并添加组合函数:

    package com.jetbrains.spacetutorial import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import org.koin.androidx.compose.koinViewModel @OptIn( ExperimentalMaterial3Api::class ) @Composable fun App() { val viewModel = koinViewModel<RocketLaunchViewModel>() val state by remember { viewModel.state } val pullToRefreshState = rememberPullToRefreshState() if (pullToRefreshState.isRefreshing) { viewModel.loadLaunches() pullToRefreshState.endRefresh() } }

    使用Koin ViewModel API引用Android Koin模块中声明的viewModel

  2. 添加实现加载屏幕、发射结果列和下拉刷新操作的UI代码:

    import com.jetbrains.spacetutorial.theme.AppTheme import com.jetbrains.spacetutorial.entity.RocketLaunch import com.jetbrains.spacetutorial.theme.app_theme_successful import com.jetbrains.spacetutorial.theme.app_theme_unsuccessful import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.pulltorefresh.PullToRefreshContainer import androidx.compose.ui.Modifier import androidx.compose.ui.Alignment import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp @Composable fun App() { // ... AppTheme { Scaffold( topBar = { TopAppBar(title = { Text( "SpaceX Launches", style = MaterialTheme.typography.headlineLarge ) }) } ) { padding -> Box( modifier = Modifier .nestedScroll(pullToRefreshState.nestedScrollConnection) .fillMaxSize() .padding(padding) ) { if (state.isLoading) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text("Loading...", style = MaterialTheme.typography.bodyLarge) } } else { LazyColumn { items(state.launches) { launch: RocketLaunch -> Column(modifier = Modifier.padding(all = 16.dp)) { Text( text = "${launch.missionName} - ${launch.launchYear}", style = MaterialTheme.typography.headlineSmall ) Spacer(Modifier.height(8.dp)) Text( text = if (launch.launchSuccess == true) "Successful" else "Unsuccessful", color = if (launch.launchSuccess == true) app_theme_successful else app_theme_unsuccessful ) Spacer(Modifier.height(8.dp)) val details = launch.details if (details?.isNotBlank() == true) { Text( text = details ) } } HorizontalDivider() } } } PullToRefreshContainer( state = pullToRefreshState, modifier = Modifier.align(Alignment.TopCenter) ) } } } }

  3. 删除com.jetbrains.spacetutorial包中MainActivity.kt文件的import App行,使setContent()函数引用刚创建的组合函数

  4. AndroidManifest.xml文件中指定MainActivity类:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <application ... <activity ... android:name="com.jetbrains.spacetutorial.MainActivity"> ... </activity> </application> </manifest>
  5. 运行Android应用:从运行配置菜单选择composeApp ,选择模拟器后点击运行按钮 应用将自动执行API请求并显示发射列表(背景颜色取决于生成的Material主题):

    Android应用

您已创建了一个业务逻辑在Kotlin跨平台模块中实现,UI使用原生Jetpack Compose构建的Android应用

创建iOS应用

对于iOS部分,将使用SwiftUI构建用户界面,并采用模型-视图-视图模型模式。

Kotlin跨平台向导生成的iOS项目已连接到共享模块。Kotlin模块以shared/build.gradle.kts文件中指定的名称导出(baseName = "Shared" ),并通过常规import语句导入: import Shared

为SQLDelight添加动态链接标志

默认情况下,Kotlin跨平台向导生成的项目设置为iOS框架的静态链接。

要使用iOS上的原生SQLDelight驱动,需添加允许Xcode工具查找系统提供的SQLite二进制文件的动态链接器标志:

  1. 在Android Studio中右键点击iosApp/iosApp.xcodeproj文件夹选择Open In | Xcode

  2. 在Xcode中双击项目名称打开设置

  3. 切换到Build Settings标签页搜索Other Linker Flags字段

  4. 双击字段值点击**+**并添加-lsqlite3字符串

为iOS依赖注入准备Koin类

要在Swift代码中使用Koin类和函数,需创建特殊的KoinComponent类并声明iOS的Koin模块。

  1. shared/src/iosMain/kotlin/源集创建com/jetbrains/spacetutorial/KoinHelper.kt文件

  2. 添加包装SpaceXSDK类并延迟注入Koin的KoinHelper类:

    package com.jetbrains.spacetutorial import org.koin.core.component.KoinComponent import com.jetbrains.spacetutorial.entity.RocketLaunch import org.koin.core.component.inject class KoinHelper : KoinComponent { private val sdk: SpaceXSDK by inject<SpaceXSDK>() suspend fun getLaunches(forceReload: Boolean): List<RocketLaunch> { return sdk.getLaunches(forceReload = forceReload) } }
  3. 添加将在Swift中用于初始化和启动iOS Koin模块的initKoin函数:

    import com.jetbrains.spacetutorial.cache.IOSDatabaseDriverFactory import com.jetbrains.spacetutorial.network.SpaceXApi import org.koin.core.context.startKoin import org.koin.dsl.module fun initKoin() { startKoin { modules(module { single<SpaceXApi> { SpaceXApi() } single<SpaceXSDK> { SpaceXSDK( databaseDriverFactory = IOSDatabaseDriverFactory(), api = get() ) } }) } }

现在可以在iOS应用中启动Koin模块,将原生数据库驱动与通用的SpaceXSDK类结合使用

实现UI

首先创建用于显示列表项的RocketLaunchRow SwiftUI视图。它将基于`H

23 四月 2025