使用 Kotlin Multiplatform 构建全栈应用程序
使用 Kotlin Multiplatform 构建全栈应用程序
代码示例: full-stack-task-manager
使用的插件:
在本文中,你将学习如何使用 Kotlin 开发运行在 Android、iOS、Web 和桌面平台的全栈应用程序,同时利用 Ktor 进行无缝的数据处理。
在本教程结束时,你将了解如何执行以下操作:
- 使用 Kotlin Multiplatform 创建全栈应用程序。
- 理解由 IntelliJ IDEA 生成的项目。
- 创建调用 Ktor 服务的 Compose Multiplatform 客户端。
- 在设计的不同层级中复用共享类型。
- 正确包含和配置多平台库。
在之前的教程中,我们使用任务管理器(Task Manager)示例来
你将创建一个面向 Android、iOS、Web 和桌面平台的客户端,并使用 Ktor 服务获取要显示的数据。你将尽可能在客户端和服务器之间共享数据类型,从而加快开发速度并减少出错的可能性。
前提条件
与之前的文章一样,你将使用 IntelliJ IDEA 作为 IDE。要安装和配置环境,请参阅 Kotlin Multiplatform 快速入门指南 。
如果这是你第一次使用 Compose Multiplatform,我们建议你在开始本教程之前先完成 Compose Multiplatform 入门 教程。为了降低任务的复杂度,你可以专注于单一的客户端平台。例如,如果你从未用过 iOS,那么专注于桌面端或 Android 开发可能是明智之举。
创建一个新项目
不使用 Ktor 项目生成器,而是使用 IntelliJ IDEA 中的 Kotlin Multiplatform 项目向导。 它将创建一个基础的多平台项目,你可以通过客户端和服务对其进行扩展。客户端可以使用原生 UI 库(如 SwiftUI),但在本教程中,你将使用 Compose Multiplatform 为所有平台创建一个共享 UI。
- 启动 IntelliJ IDEA。
- 在 IntelliJ IDEA 中,选择 File | New | Project 。
- 在左侧面板中,选择 Kotlin Multiplatform 。
- 在 New Project 窗口中指定以下字段:
- Name : full-stack-task-manager
- Project ID : com.example.ktor
选择 Android 、 Desktop 、 Web 和 Server 作为目标平台。
如果你使用的是 Mac,也请选择 iOS 。确保已选中 Share UI 选项。

点击 Create 按钮并等待 IDE 生成并导入项目。
运行服务
- 在 IntelliJ IDEA 中,选择 ApplicationKt 运行配置。

- 点击 Run 按钮 (
) 以运行该配置。
Run 工具窗口中将打开一个新标签页。
导航至 http://0.0.0.0:8080/ 以打开应用程序。 你应该会看到浏览器中显示的来自 Ktor 的消息。

检查项目
server 文件夹是项目中的三个 Kotlin 模块之一。另外两个是 core 和 app 。
server 模块的结构与 Ktor 项目生成器 生成的结构非常相似。 你拥有一个专门的构建文件来声明插件和依赖项,以及一个包含用于构建和启动 Ktor 服务代码的源集:

如果你查看 Application.kt 文件中的路由指令,你会看到对 sayHello() 函数的调用:
sayHello() 函数定义在 core 模块中。这是你放置要在服务器和所有不同客户端平台之间共享的通用代码的地方。
打开 app/shared/src/commonMain 模块中的 Greeting.kt 文件,你会看到 sayHello() 函数也在那里被使用了:
app 模块包含以下子模块:
- androidApp、desktopApp、iosApp 和 webApp 子模块分别包含针对 Android、桌面端、iOS 和 Web 客户端应用的平台特定代码。目前,这些客户端应用都没有链接到 Ktor 服务。
shared 子模块为你希望提供客户端的每个平台都包含一个源集。这是因为 commonMain 中声明的类型需要的功能因目标平台而异。
例如,在
Greeting类型中,当前平台的名称是使用平台特定的 API,通过 预期声明 (expect) 和实际声明 (actual) 获取的。在 shared 子模块的 commonMain 源集中,
getPlatform()函数使用expect关键字声明:kotlin然后,每个目标平台都提供
getPlatform()函数的一个actual声明,如下所示:kotlinkotlinkotlinkotlin
运行客户端应用程序
你可以通过执行目标的运行配置来运行客户端应用程序。要在 iOS 模拟器(Simulator)上运行应用程序,请按照以下步骤操作:
- 在 IntelliJ IDEA 中,选择 iosApp 运行配置和一个模拟设备。

- 点击 Run 按钮 (
) 以运行该配置。
运行 iOS 应用时,它会在后台通过 Xcode 进行构建并在 iOS 模拟器中启动。 应用会显示一个按钮,点击后可以切换图片的显示。

第一次按下按钮时,当前平台的详细信息会添加到按钮文本中。实现此功能的代码位于 app/shared/src/commonMain/kotlin/com/example/ktor/App.kt :
kotlin这是一个可组合函数,你将在本文后面部分对其进行修改。目前,重要的是它显示了一个 UI,并使用了共享的
Greeting类型,而该类型又使用了实现通用Platform接口的平台特定类。
现在你已经了解了生成的项目的结构,可以逐步添加任务管理器功能。
添加模型类型
首先,添加模型类型,并确保它们对于客户端和服务器都是可访问的。
- 导航至 gradle/libs.versions.toml 并定义以下
kotlinx.serialization依赖项:toml 导航至 core/build.gradle.kts 并添加序列化插件:
kotlin在同一文件中,向 commonMain 源集添加一个新的依赖项:
kotlin- 在 IntelliJ IDEA 中,选择 Build | Sync Project with Gradle Files 以应用更新。Gradle 导入完成后,你应该会发现你的 Task.kt 文件编译成功。
- 导航至 core/src/commonMain/kotlin/com/example/ktor 并创建一个名为 model 的新包。
- 在新包内,创建一个名为 Task.kt 的新文件。
添加一个表示优先级的枚举和一个表示任务的类。
Task类使用来自kotlinx.serialization库的Serializable注解:kotlin
创建服务器
下一阶段是为任务管理器创建服务器实现。
- 导航至 server/src/main/kotlin/com/example/ktor 文件夹并创建一个名为 model 的子包。
在此包内,创建一个新的 TaskRepository.kt 文件,并为该仓库添加以下接口:
kotlin在同一包中,创建一个名为 InMemoryTaskRepository.kt 的新文件,其中包含以下类:
kotlin导航至 server/src/main/kotlin/.../Application.kt 并将现有代码替换为以下实现:
kotlin此实现与之前教程中的非常相似,不同之处在于,为了简单起见,现在你已将所有路由代码放置在
Application.module()函数中。输入此代码并添加导入后,你会发现多个编译器错误,因为代码使用了多个需要作为依赖项包含的 Ktor 插件,包括用于与 Web 客户端交互的
CORS插件。所需依赖项:io.ktor:%artifact_name%- 打开 gradle/libs.versions.toml 文件并定义以下库: toml
打开服务器模块构建文件 ( server/build.gradle.kts ) 并添加以下依赖项:
kotlin- 再次从主菜单中选择 Build | Sync Project with Gradle Files。 导入完成后,你应该会发现
ContentNegotiation类型和json()函数的导入可以正常工作。 - 重新运行服务器。你应该会发现路由可以通过浏览器访问。
创建客户端
为了让你的客户端能够访问服务器,你需要包含 Ktor Client。这涉及三种类型的依赖项:
- Ktor Client 的核心功能。
- 处理网络连接的平台特定引擎。
- 对内容协商和序列化的支持。
- 在 gradle/libs.versions.toml 文件中,添加以下库: toml
- 导航至 app/shared/build.gradle.kts 并添加以下依赖项: kotlin
完成后,你可以添加一个
TaskApi类型,作为你的客户端围绕 Ktor Client 的薄封装。 - 从主菜单中选择 Build | Sync Project with Gradle Files 以导入构建文件中的更改。
- 导航至 app/shared/src/commonMain/kotlin/com/example/ktor 并创建一个名为 network 的新包。
在新包内,创建一个新的 HttpClientManager.kt 文件用于客户端配置:
kotlin将
1.2.3.4替换为你当前机器的 IP 地址。你将无法从运行在 Android 虚拟设备或 iOS 模拟器上的代码中调用0.0.0.0或localhost。TIP
查找你的 IP 地址:
由于移动端模拟器无法访问
localhost,你需要机器的实际 IP 地址。要查找你的 IP 地址,请运行以下命令之一:- macOS:
ifconfig | grep "inet " | grep -v 127.0.0.1 - Linux:
hostname -I | awk '{print $1}' - Windows:
ipconfig并查找“IPv4 地址”
- macOS:
在同一个 app/shared/.../network 包中,创建一个具有以下实现的新 TaskApi.kt 文件:
kotlin导航至 app/shared/.../App.kt 并使用以下实现替换代码。 这将使用
TaskApi类型从服务器检索任务列表,然后在一个列中显示每个任务的名称:kotlin在服务器运行的同时,通过运行 iosApp 运行配置来测试 iOS 应用程序。
点击 Fetch Tasks 按钮显示任务列表:

NOTE
在本次演示中,为了清晰起见,我们简化了流程。在现实世界的应用中,避免在网络上发送未加密的数据至关重要。在 Android 平台上,你需要明确授予应用程序网络权限,并允许其以明文形式发送和接收数据。要启用这些权限,请打开 app/androidApp/src/main/AndroidManifest.xml 并添加以下设置:
xml使用 app.androidApp 运行配置运行 Android 应用程序。 你应该会发现你的 Android 客户端现在也能正常运行了:

对于桌面端客户端,你将为包含的窗口分配尺寸和标题。 打开文件 app/desktopApp/src/.../main.kt 并通过更改
title和设置state属性来修改代码:kotlin使用 app [hot] 🔥 运行配置运行桌面端应用程序:

使用以下运行配置之一运行 Web 客户端:
- app [js]:运行你的 Kotlin/JS 应用程序。
- app [wasmJs]:运行你的 Kotlin/Wasm 应用程序。

美化 UI
现在客户端正在与服务器通信,但其 UI 显然不够吸引人。
打开位于 app/shared/src/commonMain/.../ktor 的 App.kt 文件,并使用下面的
App和TaskCard可组合项替换现有的App:kotlin通过这一实现,你的客户端现在已经具备了一些基本功能。
通过使用
LaunchedEffect类型,所有任务都会在启动时加载,而LazyColumn可组合项允许用户滚动浏览任务。最后,创建了一个独立的
TaskCard可组合项,它反过来使用Card来显示每个Task的详细信息。添加了用于删除和更新任务的按钮。重新运行客户端应用程序——例如 Android 应用。 你现在可以滚动浏览任务、查看其详细信息并删除它们:

添加更新功能
为了完善客户端,加入允许更新任务详细信息的功能。
- 导航至 app/shared/src/commonMain/.../ktor 中的 App.kt 文件。
添加
UpdateTaskDialog可组合项和必要的导入,如下所示:kotlin这是一个使用对话框显示
Task详细信息的可组合项。description和priority被放置在TextField可组合项中,以便它们可以被更新。当用户按下更新按钮时,它会触发onConfirm()回调。在同一个文件中更新
App可组合项:kotlin你正在存储一个额外的状态片段,即当前选定的任务。如果该值不为 null,那么我们就调用
UpdateTaskDialog可组合项,并将onConfirm()回调设置为使用TaskApi向服务器发送 POST 请求。最后,在创建
TaskCard可组合项时,你使用onUpdate()回调来设置currentTask状态变量。- 重新运行客户端应用程序。你现在应该能够使用这些按钮更新每个任务的详细信息。

后续步骤
在本文中,你已在 Kotlin Multiplatform 应用程序的环境中使用了 Ktor。你现在可以创建一个包含多个服务和客户端的项目,目标平台涵盖一系列不同的平台。
如你所见,构建功能而不产生任何代码重复或冗余是可能的。项目各层所需的类型可以放置在 core 多平台模块中。仅由服务需要的功能放置在 server 模块中,而仅由客户端需要的功能则放置在 app 模块中。
这种开发方式不可避免地需要客户端和服务器端技术的知识。但你可以使用 Kotlin Multiplatform 库和 Compose Multiplatform 来最大限度地减少你需要学习的新材料。即使你的重点最初只在单一平台上,你也可以随着对应用程序需求的增长轻松添加其他平台。

