Skip to content

使用 Kotlin 多平台建置全端應用程式

程式碼範例: full-stack-task-manager

使用的外掛程式:

Routing
路由 (Routing) 是伺服器應用程式中用於處理傳入請求的核心外掛程式。
kotlinx.serialization
Content Negotiation
ContentNegotiation 外掛程式有兩個主要目的:在用戶端與伺服器之間協商媒體類型,並以特定格式序列化/反序列化內容。
Compose MultiplatformKotlin Multiplatform

在本文中,您將學習如何使用 Kotlin 開發一個全端應用程式,它可以在 Android、iOS 和桌面平台運行,同時利用 Ktor 進行無縫資料處理。

完成本教學後,您將了解如何執行以下操作:

  • 使用 Kotlin Multiplatform 建立全端應用程式。
  • 理解使用 IntelliJ IDEA 產生的專案。
  • 建立呼叫 Ktor 服務的 Compose Multiplatform 用戶端。
  • 在設計的不同層級之間重複使用共享類型。
  • 正確包含和配置多平台函式庫。

在之前的教學中,我們使用任務管理器 (Task Manager) 範例來

處理請求
透過建置任務管理器應用程式,學習使用 Kotlin 和 Ktor 進行路由、處理請求和參數的基本知識。
建立 RESTful API
學習如何使用 Kotlin 和 Ktor 建置後端服務,其中包含產生 JSON 檔案的 RESTful API 範例。
,以及
整合 Exposed 資料庫
學習將 Ktor 服務連接到 Exposed SQL 函式庫資料庫儲存庫的過程。
。 用戶端應用程式盡可能保持簡約,以便您能專注於學習 Ktor 的基礎知識。

您將建立一個針對 Android、iOS 和桌面平台的用戶端,使用 Ktor 服務來 獲取要顯示的資料。在可能的情況下,您將在用戶端和伺服器之間共享資料類型, 加快開發速度並減少潛在錯誤。

先決條件

與之前的文章一樣,您將使用 IntelliJ IDEA 作為 IDE。要安裝和配置您的 環境,請參閱 Kotlin Multiplatform 快速入門 指南。

如果這是您第一次使用 Compose Multiplatform,我們建議您先完成 Compose Multiplatform 入門 教學,再開始本教學。為了降低任務的複雜性,您可以專注於單一用戶端平台。例如,如果您從未使用過 iOS,那麼專注於桌面或 Android 開發可能更明智。

建立新專案

請改用 IntelliJ IDEA 中的 Kotlin Multiplatform 專案精靈,而非 Ktor 專案產生器。 它將建立一個基本的多平台專案,您可以擴展該專案以包含 用戶端和服務。用戶端既可以使用原生 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
    • Group : com.example.ktor
  5. 選擇 AndroidDesktopServer 作為目標平台。

  6. 如果您使用的是 Mac,也請選擇 iOS 。確保已選取 Share UI 選項。 Kotlin Multiplatform 精靈設定

  7. 按一下 Create 按鈕,等待 IDE 產生並匯入專案。

執行服務

  1. Project 視圖中,導覽至 server/src/main/kotlin/com/example/ktor/full_stack_task_manager 並開啟 Application.kt 檔案。
  2. 按一下 Run 按鈕 (IntelliJ IDEA 執行圖示) 在 main() 函式旁邊以啟動應用程式。

    Run 工具視窗中將開啟一個新分頁,日誌結尾顯示訊息「Responding at http://0.0.0.0:8080」。

  3. 導覽至 http://0.0.0.0:8080/ 以開啟應用程式。 您應該會在瀏覽器中看到 Ktor 顯示的訊息。 Ktor 伺服器瀏覽器回應

檢查專案

server 資料夾是專案中三個 Kotlin 模組之一。另外兩個是 sharedcomposeApp

server 模組的結構與 Ktor Project Generator 產生的結構非常相似。 您有一個專用的建置檔案來 宣告外掛程式和相依性,以及一個包含用於建置和啟動 Ktor 服務的程式碼來源集:

Kotlin Multiplatform 專案中伺服器資料夾的內容

如果您查看 Application.kt 檔案中的路由指令,您將看到對 greet() 函式的呼叫:

kotlin

這會建立一個 Greeting 類型的實例並調用其 greet() 方法。 Greeting 類別定義在 shared 模組中: Greeting.kt 和 Platform.kt 在 IntelliJ IDEA 中開啟

shared 模組包含將在不同目標平台中使用的程式碼。

shared 模組集中的 commonMain 來源集保存著將在所有平台中使用的類型。 如您所見,這就是 Greeting 類型定義的地方。 這也是您將放置伺服器和所有不同用戶端平台之間共享的通用程式碼的地方。

shared 模組還包含您希望提供用戶端的每個平台的來源集。這是因為 在 commonMain 中宣告的類型可能需要因目標平台而異的功能。對於 Greeting 類型,您希望使用平台特定 API 獲取當前平台的名稱。 這透過 預期 (expected) 和實際 (actual) 宣告實現。

shared 模組的 commonMain 來源集中,您使用 expect 關鍵字宣告 getPlatform() 函式:

kotlin

然後每個目標平台 必須提供 getPlatform() 函式的 actual 宣告,如下所示:

kotlin
kotlin
kotlin
kotlin

專案中還有一個額外的模組,即 composeApp 模組。 它包含 Android、iOS、桌面和網頁用戶端應用程式的程式碼。 這些應用程式目前未連結到 Ktor 服務,但它們確實使用了共享的 Greeting 類別。

執行用戶端應用程式

您可以透過執行目標的執行設定來執行用戶端應用程式。要在 iOS 模擬器上運行 應用程式,請按照以下步驟操作:

  1. 在 IntelliJ IDEA 中,選擇 iosApp 執行設定和一個模擬裝置。 執行與偵錯視窗
  2. 按一下 Run 按鈕 (IntelliJ IDEA 執行圖示) 來執行設定。
  3. 當您執行 iOS 應用程式時,它會在幕後使用 Xcode 建置,並在 iOS 模擬器中啟動。 應用程式會顯示一個按鈕,點擊時會切換圖片。 在 iOS 模擬器中執行應用程式

    首次按下按鈕時,會將當前平台的詳細資訊新增到其文字中。實現此目的的程式碼位於 composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager/App.kt

    kotlin

    這是一個可組合函式,您將在本文稍後修改它。目前重要的是它會顯示一個 UI,並使用共享的 Greeting 類型,而 Greeting 類型又使用了實現通用 Platform 介面的平台特定類別。

現在您已了解生成專案的結構,您可以逐步新增任務 管理員功能。

新增模型類型

首先,新增模型類型並確保用戶端和伺服器都可以存取它們。

  1. 導覽至 shared/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 並建立一個名為 model 的新套件。
  2. 在新套件內,建立一個名為 Task.kt 的新檔案。
  3. 新增一個 enum 來表示優先級,以及一個 class 來表示任務。 Task 類別使用 kotlinx.serialization 函式庫中的 Serializable 類型進行註解:

    kotlin

    您會注意到匯入和註解都無法編譯。這是因為專案尚未 對 kotlinx.serialization 函式庫有相依性。

  4. 導覽至 shared/build.gradle.kts 並新增序列化外掛程式:

    kotlin
  5. 在同一個檔案中,為 commonMain 來源集新增一個相依性:

    kotlin
  6. 導覽至 gradle/libs.versions.toml 並定義以下內容:
    toml
  7. 在 IntelliJ IDEA 中,選擇 Build | Sync Project with Gradle Files 以套用更新。一旦 Gradle 匯入完成,您應該會發現您的 Task.kt 檔案成功編譯。

請注意,如果沒有包含序列化外掛程式,程式碼也能夠編譯,然而, 透過網路序列化 Task 物件所需的類型將不會產生。這會導致在嘗試調用服務時出現執行時期錯誤。

將序列化外掛程式放置在另一個模組(例如 servercomposeApp )不會在建置時期造成錯誤。但同樣地,序列化所需的額外類型將不會產生,導致執行時期錯誤。

建立伺服器

下一個階段是為我們的任務管理器建立伺服器實作。

  1. 導覽至 server/src/main/kotlin/com/example/ktor/full_stack_task_manager 資料夾並建立一個名為 model 的子套件。
  2. 在此套件內,建立一個新的 TaskRepository.kt 檔案並為我們的儲存庫新增以下介面:

    kotlin
  3. 在同一個套件中,建立一個名為 InMemoryTaskRepository.kt 的新檔案,其中包含以下類別:

    kotlin
  4. 導覽至 server/src/main/kotlin/.../Application.kt 並將現有程式碼替換為以下實作:

    kotlin

    此實作與之前的教學非常相似,不同之處在於,為了簡化起見,現在您已將所有路由程式碼放置在 Application.module() 函式中。

    輸入此程式碼並新增匯入後,您將發現多個編譯器錯誤,因為程式碼使用了多個需要包含為相依性的 Ktor 外掛程式, 包括用於與網頁用戶端互動的

    CORS
    所需相依性:io.ktor:%artifact_name% 程式碼範例:full-stack-task-manager 原生伺服器支援:✅
    外掛程式。

  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. 導覽至 composeApp/build.gradle.kts 並新增以下相依性:
    kotlin

    完成此操作後,您可以為用戶端新增一個 TaskApi 類型,作為 Ktor Client 的輕量封裝。

  3. 從主功能表選擇 Build | Sync Project with Gradle Files 以匯入建置檔案中的變更。
  4. 導覽至 composeApp/src/commonMain/kotlin/com/example/ktor/full_stack_task_manager 並建立一個名為 network 的新套件。
  5. 在新套件內,為用戶端配置建立一個新的 HttpClientManager.kt

    kotlin

    請注意,您應該將 1.2.3.4 替換為您當前機器的 IP 位址。您將 無法從 Android 虛擬裝置或 iOS 模擬器上運行的程式碼向 0.0.0.0localhost 發出呼叫。

  6. 在相同的 composeApp/.../full_stack_task_manager/network 套件中,建立一個新的 TaskApi.kt 檔案,並加入以下實作:

    kotlin
  7. 導覽至 commonMain/.../App.kt 並將現有的 App 可組合函式替換為以下實作。 這將使用 TaskApi 類型從伺服器檢索任務列表,然後 在欄位中顯示每個任務的名稱:

    kotlin
  8. 在伺服器運行時,透過執行 iosApp 執行設定來測試 iOS 應用程式。

  9. 按一下 Fetch Tasks 按鈕以顯示任務列表: 應用程式在 iOS 上執行

    NOTE

    在此示範中,我們為求清晰而簡化了流程。在實際應用中,避免透過網路傳送未加密的資料至關重要。
  10. 在 Android 平台上,您需要明確授予應用程式網路權限並 允許它以明文形式傳送和接收資料。要啟用這些權限,請開啟 composeApp/src/androidMain/AndroidManifest.xml 並新增以下設定:

    xml
  11. 使用 composeApp 執行設定來執行 Android 應用程式。 您現在應該會發現您的 Android 用戶端也能運行: 應用程式在 Android 上執行

  12. 對於桌面用戶端,您應該為包含視窗指定尺寸和標題。 開啟 composeApp/src/desktopMain/.../main.kt 檔案並透過更改 title 和設定 state 屬性來修改程式碼:

    kotlin
  13. 使用 composeApp [desktop] 執行設定來執行桌面應用程式: 應用程式在桌面上執行

  14. 使用 composeApp [wasmJs] 執行設定來執行網頁用戶端:

    應用程式在桌面上執行

改進使用者介面 (UI)

用戶端現在正在與伺服器通訊,但這很難說是一個美觀的 UI。

  1. 開啟位於 composeApp/src/commonMain/.../full_stack_task_manager 中的 App.kt 檔案,並將現有的 App 替換為以下的 AppTaskCard 可組合函式:

    kotlin

    透過此實作,您的用戶端現在擁有了一些基本功能。

    透過使用 LaunchedEffect 類型,所有任務在啟動時載入,而 LazyColumn 可組合函式允許使用者捲動瀏覽任務。

    最後,建立了一個單獨的 TaskCard 可組合函式,它又使用一個 Card 來顯示每個 Task 的詳細資訊。已經新增了按鈕以 刪除和更新任務。

  2. 重新執行用戶端應用程式 — 例如 Android 應用程式。 您現在可以捲動瀏覽任務、查看其詳細資訊並刪除它們: 應用程式在 Android 上以改進的 UI 運行

新增更新功能

為了完善用戶端,請整合允許更新任務詳細資訊的功能。

  1. 導覽至 composeApp/src/commonMain/.../full_stack_task_manager 中的 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。您現在可以 建立一個包含多個服務和用戶端,針對各種不同平台的專案。

如您所見,在沒有任何程式碼重複或冗餘的情況下建置功能是可能的。 專案所有層所需的類型可以放置在 shared 多平台模組中。 服務才需要的功能放在 server 模組中,而用戶端才需要的功能則放在 composeApp 中。

這種開發不可避免地需要同時具備用戶端和伺服器技術的知識。但您可以使用 Kotlin Multiplatform 函式庫和 Compose Multiplatform 來最大程度地減少您需要學習的新內容。即使您最初 只專注於單一平台,隨著您的應用程式需求增長,您也可以輕鬆新增其他平台。