Skip to content

使用 Kotlin Multiplatform 构建全栈应用程序

使用 Kotlin Multiplatform 构建全栈应用程序

代码示例 full-stack-task-manager

使用的插件

Routing
路由是服务器应用程序中处理传入请求的核心插件。
kotlinx.serialization
Content Negotiation
ContentNegotiation 插件有两个主要目的:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
Compose MultiplatformKotlin Multiplatform

在本文中,你将学习如何使用 Kotlin 开发运行在 Android、iOS、Web 和桌面平台的全栈应用程序,同时利用 Ktor 进行无缝的数据处理。

在本教程结束时,你将了解如何执行以下操作:

  • 使用 Kotlin Multiplatform 创建全栈应用程序。
  • 理解由 IntelliJ IDEA 生成的项目。
  • 创建调用 Ktor 服务的 Compose Multiplatform 客户端。
  • 在设计的不同层级中复用共享类型。
  • 正确包含和配置多平台库。

在之前的教程中,我们使用任务管理器(Task Manager)示例来

处理请求
通过构建任务管理器应用程序,学习在 Kotlin 中使用 Ktor 处理请求、路由和参数的基础知识。
创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含一个生成 JSON 文件的 RESTful API 示例。
以及
使用 Exposed 集成数据库
了解使用 Exposed SQL 库将 Ktor 服务连接到数据库仓库的过程。
。 当时客户端应用程序尽可能保持简单,以便你可以专注于学习 Ktor 的基础知识。

你将创建一个面向 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。

  1. 启动 IntelliJ IDEA。
  2. 在 IntelliJ IDEA 中,选择 File | New | Project
  3. 在左侧面板中,选择 Kotlin Multiplatform
  4. New Project 窗口中指定以下字段:
    • Name : full-stack-task-manager
    • Project ID : com.example.ktor
  5. 选择 AndroidDesktopWebServer 作为目标平台。

  6. 如果你使用的是 Mac,也请选择 iOS 。确保已选中 Share UI 选项。 Kotlin Multiplatform 向导设置

  7. 点击 Create 按钮并等待 IDE 生成并导入项目。

运行服务

  1. 在 IntelliJ IDEA 中,选择 ApplicationKt 运行配置。 运行和调试窗口
  2. 点击 Run 按钮 (IntelliJ IDEA 运行图标) 以运行该配置。

    Run 工具窗口中将打开一个新标签页。

  3. 导航至 http://0.0.0.0:8080/ 以打开应用程序。 你应该会看到浏览器中显示的来自 Ktor 的消息。 Ktor 服务器浏览器响应

检查项目

server 文件夹是项目中的三个 Kotlin 模块之一。另外两个是 coreapp

server 模块的结构与 Ktor 项目生成器 生成的结构非常相似。 你拥有一个专门的构建文件来声明插件和依赖项,以及一个包含用于构建和启动 Ktor 服务代码的源集:

Kotlin Multiplatform 项目中 server 文件夹的内容

如果你查看 Application.kt 文件中的路由指令,你会看到对 sayHello() 函数的调用:

kotlin

sayHello() 函数定义在 core 模块中。这是你放置要在服务器和所有不同客户端平台之间共享的通用代码的地方。

打开 app/shared/src/commonMain 模块中的 Greeting.kt 文件,你会看到 sayHello() 函数也在那里被使用了:

kotlin

app 模块包含以下子模块:

  • androidAppdesktopAppiosAppwebApp 子模块分别包含针对 Android、桌面端、iOS 和 Web 客户端应用的平台特定代码。目前,这些客户端应用都没有链接到 Ktor 服务。
  • shared 子模块为你希望提供客户端的每个平台都包含一个源集。这是因为 commonMain 中声明的类型需要的功能因目标平台而异。

    例如,在 Greeting 类型中,当前平台的名称是使用平台特定的 API,通过 预期声明 (expect) 和实际声明 (actual) 获取的。

    shared 子模块的 commonMain 源集中,getPlatform() 函数使用 expect 关键字声明:

    kotlin

    然后,每个目标平台都提供 getPlatform() 函数的一个 actual 声明,如下所示:

    kotlin
    kotlin
    kotlin
    kotlin

运行客户端应用程序

你可以通过执行目标的运行配置来运行客户端应用程序。要在 iOS 模拟器(Simulator)上运行应用程序,请按照以下步骤操作:

  1. 在 IntelliJ IDEA 中,选择 iosApp 运行配置和一个模拟设备。 运行和调试窗口
  2. 点击 Run 按钮 (IntelliJ IDEA 运行图标) 以运行该配置。
  3. 运行 iOS 应用时,它会在后台通过 Xcode 进行构建并在 iOS 模拟器中启动。 应用会显示一个按钮,点击后可以切换图片的显示。 在 iOS 模拟器中运行应用

    第一次按下按钮时,当前平台的详细信息会添加到按钮文本中。实现此功能的代码位于 app/shared/src/commonMain/kotlin/com/example/ktor/App.kt

    kotlin

    这是一个可组合函数,你将在本文后面部分对其进行修改。目前,重要的是它显示了一个 UI,并使用了共享的 Greeting 类型,而该类型又使用了实现通用 Platform 接口的平台特定类。

现在你已经了解了生成的项目的结构,可以逐步添加任务管理器功能。

添加模型类型

首先,添加模型类型,并确保它们对于客户端和服务器都是可访问的。

  1. 导航至 gradle/libs.versions.toml 并定义以下 kotlinx.serialization 依赖项:
    toml
  2. 导航至 core/build.gradle.kts 并添加序列化插件:

    kotlin
  3. 在同一文件中,向 commonMain 源集添加一个新的依赖项:

    kotlin
  4. 在 IntelliJ IDEA 中,选择 Build | Sync Project with Gradle Files 以应用更新。Gradle 导入完成后,你应该会发现你的 Task.kt 文件编译成功。
  5. 导航至 core/src/commonMain/kotlin/com/example/ktor 并创建一个名为 model 的新包。
  6. 在新包内,创建一个名为 Task.kt 的新文件。
  7. 添加一个表示优先级的枚举和一个表示任务的类。 Task 类使用来自 kotlinx.serialization 库的 Serializable 注解:

    kotlin

创建服务器

下一阶段是为任务管理器创建服务器实现。

  1. 导航至 server/src/main/kotlin/com/example/ktor 文件夹并创建一个名为 model 的子包。
  2. 在此包内,创建一个新的 TaskRepository.kt 文件,并为该仓库添加以下接口:

    kotlin
  3. 在同一包中,创建一个名为 InMemoryTaskRepository.kt 的新文件,其中包含以下类:

    kotlin
  4. 导航至 server/src/main/kotlin/.../Application.kt 并将现有代码替换为以下实现:

    kotlin

    此实现与之前教程中的非常相似,不同之处在于,为了简单起见,现在你已将所有路由代码放置在 Application.module() 函数中。

    输入此代码并添加导入后,你会发现多个编译器错误,因为代码使用了多个需要作为依赖项包含的 Ktor 插件,包括用于与 Web 客户端交互的

    CORS
    所需依赖项:io.ktor:%artifact_name%
    插件。

  5. 打开 gradle/libs.versions.toml 文件并定义以下库:
    toml
  6. 打开服务器模块构建文件 ( server/build.gradle.kts ) 并添加以下依赖项:

    kotlin
  7. 再次从主菜单中选择 Build | Sync Project with Gradle Files。 导入完成后,你应该会发现 ContentNegotiation 类型和 json() 函数的导入可以正常工作。
  8. 重新运行服务器。你应该会发现路由可以通过浏览器访问。
  9. 导航至 以查看 JSON 格式的任务服务器响应。 浏览器中的服务器响应

创建客户端

为了让你的客户端能够访问服务器,你需要包含 Ktor Client。这涉及三种类型的依赖项:

  • Ktor Client 的核心功能。
  • 处理网络连接的平台特定引擎。
  • 对内容协商和序列化的支持。
  1. gradle/libs.versions.toml 文件中,添加以下库:
    toml
  2. 导航至 app/shared/build.gradle.kts 并添加以下依赖项:
    kotlin

    完成后,你可以添加一个 TaskApi 类型,作为你的客户端围绕 Ktor Client 的薄封装。

  3. 从主菜单中选择 Build | Sync Project with Gradle Files 以导入构建文件中的更改。
  4. 导航至 app/shared/src/commonMain/kotlin/com/example/ktor 并创建一个名为 network 的新包。
  5. 在新包内,创建一个新的 HttpClientManager.kt 文件用于客户端配置:

    kotlin

    1.2.3.4 替换为你当前机器的 IP 地址。你将无法从运行在 Android 虚拟设备或 iOS 模拟器上的代码中调用 0.0.0.0localhost

    TIP

    查找你的 IP 地址:

    由于移动端模拟器无法访问 localhost,你需要机器的实际 IP 地址。要查找你的 IP 地址,请运行以下命令之一:

    • macOS: ifconfig | grep "inet " | grep -v 127.0.0.1
    • Linux: hostname -I | awk '{print $1}'
    • Windows: ipconfig 并查找“IPv4 地址”
  6. 在同一个 app/shared/.../network 包中,创建一个具有以下实现的新 TaskApi.kt 文件:

    kotlin
  7. 导航至 app/shared/.../App.kt 并使用以下实现替换代码。 这将使用 TaskApi 类型从服务器检索任务列表,然后在一个列中显示每个任务的名称:

    kotlin
  8. 在服务器运行的同时,通过运行 iosApp 运行配置来测试 iOS 应用程序。

  9. 点击 Fetch Tasks 按钮显示任务列表: 在 iOS 上运行的应用

    NOTE

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

    xml
  11. 使用 app.androidApp 运行配置运行 Android 应用程序。 你应该会发现你的 Android 客户端现在也能正常运行了: 在 Android 上运行的应用

  12. 对于桌面端客户端,你将为包含的窗口分配尺寸和标题。 打开文件 app/desktopApp/src/.../main.kt 并通过更改 title 和设置 state 属性来修改代码:

    kotlin
  13. 使用 app [hot] 🔥 运行配置运行桌面端应用程序: 在桌面端运行的应用

  14. 使用以下运行配置之一运行 Web 客户端:

    • app [js]:运行你的 Kotlin/JS 应用程序。
    • app [wasmJs]:运行你的 Kotlin/Wasm 应用程序。
    在 Web 端运行的应用

美化 UI

现在客户端正在与服务器通信,但其 UI 显然不够吸引人。

  1. 打开位于 app/shared/src/commonMain/.../ktorApp.kt 文件,并使用下面的 AppTaskCard 可组合项替换现有的 App

    kotlin

    通过这一实现,你的客户端现在已经具备了一些基本功能。

    通过使用 LaunchedEffect 类型,所有任务都会在启动时加载,而 LazyColumn 可组合项允许用户滚动浏览任务。

    最后,创建了一个独立的 TaskCard 可组合项,它反过来使用 Card 来显示每个 Task 的详细信息。添加了用于删除和更新任务的按钮。

  2. 重新运行客户端应用程序——例如 Android 应用。 你现在可以滚动浏览任务、查看其详细信息并删除它们: 具有改进 UI 的 Android 应用

添加更新功能

为了完善客户端,加入允许更新任务详细信息的功能。

  1. 导航至 app/shared/src/commonMain/.../ktor 中的 App.kt 文件。
  2. 添加 UpdateTaskDialog 可组合项和必要的导入,如下所示:

    kotlin

    这是一个使用对话框显示 Task 详细信息的可组合项。descriptionpriority 被放置在 TextField 可组合项中,以便它们可以被更新。当用户按下更新按钮时,它会触发 onConfirm() 回调。

  3. 在同一个文件中更新 App 可组合项:

    kotlin

    你正在存储一个额外的状态片段,即当前选定的任务。如果该值不为 null,那么我们就调用 UpdateTaskDialog 可组合项,并将 onConfirm() 回调设置为使用 TaskApi 向服务器发送 POST 请求。

    最后,在创建 TaskCard 可组合项时,你使用 onUpdate() 回调来设置 currentTask 状态变量。

  4. 重新运行客户端应用程序。你现在应该能够使用这些按钮更新每个任务的详细信息。 在 Android 上删除任务

后续步骤

在本文中,你已在 Kotlin Multiplatform 应用程序的环境中使用了 Ktor。你现在可以创建一个包含多个服务和客户端的项目,目标平台涵盖一系列不同的平台。

如你所见,构建功能而不产生任何代码重复或冗余是可能的。项目各层所需的类型可以放置在 core 多平台模块中。仅由服务需要的功能放置在 server 模块中,而仅由客户端需要的功能则放置在 app 模块中。

这种开发方式不可避免地需要客户端和服务器端技术的知识。但你可以使用 Kotlin Multiplatform 库和 Compose Multiplatform 来最大限度地减少你需要学习的新材料。即使你的重点最初只在单一平台上,你也可以随着对应用程序需求的增长轻松添加其他平台。