Skip to content

Kotlin Multiplatformでフルスタックアプリケーションを構築する

Kotlin Multiplatformでフルスタックアプリケーションを構築する

コード例: full-stack-task-manager

使用されているプラグイン:

Routing
Routingは、サーバーアプリケーションで受信リクエストを処理するためのコアプラグインです。
kotlinx.serialization
Content Negotiation
ContentNegotiationプラグインは、クライアントとサーバー間のメディアタイプの交渉と、特定の形式でのコンテンツのシリアライズ/デシリアライズという2つの主要な目的を果たします。
Compose MultiplatformKotlin Multiplatform

この記事では、Android、iOS、Web、デスクトップの各プラットフォームで動作し、Ktorを活用してシームレスなデータ処理を行うフルスタックアプリケーションをKotlinで開発する方法を学びます。

このチュートリアルの終わりまでに、以下のことができるようになります:

  • Kotlin Multiplatformを使用してフルスタックアプリケーションを作成する。
  • IntelliJ IDEAで生成されたプロジェクトの構造を理解する。
  • Ktorサービスを呼び出すCompose Multiplatformクライアントを作成する。
  • 設計の異なるレイヤー間で共有型を再利用する。
  • マルチプラットフォームライブラリを正しく導入し、設定する。

これまでのチュートリアルでは、タスクマネージャーの例を使用して、

リクエストの処理
KotlinとKtorでタスクマネージャーアプリケーションを構築することで、ルーティング、リクエスト処理、パラメータの基本を学びます。
RESTful APIの作成
KotlinとKtorを使用して、JSONファイルを生成するRESTful APIの例を含むバックエンドサービスの構築方法を学びます。
Exposedによるデータベースの統合
Exposed SQLライブラリを使用して、Ktorサービスをデータベースリポジトリに接続するプロセスを学びます。
を行いました。 Ktorの基礎学習に集中できるよう、クライアントアプリケーションは可能な限り最小限に抑えられていました。

今回は、Android、iOS、Web、デスクトップのプラットフォームを対象としたクライアントを作成し、Ktorサービスを使用して表示するデータを取得します。可能な限りクライアントとサーバー間でデータ型を共有することで、開発をスピードアップし、エラーの可能性を減らします。

前提条件

これまでの記事と同様に、IDEとしてIntelliJ IDEAを使用します。環境のインストールと設定については、 Kotlin Multiplatform クイックスタート を参照してください。

Compose Multiplatformを初めて使用する場合は、このチュートリアルを開始する前に Compose Multiplatformを始める チュートリアルを完了することをお勧めします。タスクの複雑さを軽減するために、単一のクライアントプラットフォームに集中することもできます。例えば、iOSを使用したことがない場合は、デスクトップまたはAndroidの開発に集中するのが賢明かもしれません。

新しいプロジェクトを作成する

Ktorプロジェクトジェネレーターの代わりに、IntelliJ IDEAのKotlin Multiplatformプロジェクトウィザードを使用します。 これにより、クライアントとサービスを追加して拡張できる基本的なマルチプラットフォームプロジェクトが作成されます。クライアントはSwiftUIなどのネイティブUIライブラリを使用することもできますが、このチュートリアルでは 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. 実行 ボタン (IntelliJ IDEAの実行アイコン) をクリックして構成を実行します。

    実行 ツールウィンドウに新しいタブが開きます。

  3. ブラウザで http://0.0.0.0:8080/ にアクセスしてアプリケーションを開きます。 ブラウザにKtorからのメッセージが表示されるはずです。 ブラウザに表示されたKtorサーバーのレスポンス

プロジェクトを詳しく見る

server フォルダーは、プロジェクト内にある3つのKotlinモジュールの1つです。残りの2つは 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 型では、期待宣言と実効宣言 (expected and actual declarations) を通じて、プラットフォーム固有の API を使用して現在のプラットフォームの名前を取得します。

    shared サブモジュールの commonMain ソースセットでは、getPlatform() 関数が expect キーワードとともに宣言されています:

    kotlin

    次に、以下に示すように、各ターゲットプラットフォームが getPlatform() 関数の actual 宣言を提供します:

    kotlin
    kotlin
    kotlin
    kotlin

クライアントアプリケーションを実行する

ターゲットの実行構成を実行することで、クライアントアプリケーションを起動できます。iOSシミュレーターでアプリケーションを実行するには、以下の手順に従ってください:

  1. IntelliJ IDEAで、 iosApp の実行構成とシミュレートされたデバイスを選択します。 実行とデバッグのウィンドウ
  2. 実行 ボタン (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. 優先度を表す列挙型(enum)と、タスクを表すクラスを追加します。 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() 関数内に配置している点が異なります。

    このコードを入力してインポートを追加すると、複数のコンパイルエラーが発生します。これは、Webクライアントとの対話に必要な

    CORS
    必須の依存関係: io.ktor:%artifact_name%
    プラグインなど、依存関係として含める必要がある複数のKtorプラグインをコードが使用しているためです。

  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を含める必要があります。これには以下の3種類の依存関係が関係します:

  • Ktor Clientのコア機能。
  • ネットワーク処理を行うプラットフォーム固有のエンジン。
  • コンテンツ交渉とシリアライズのサポート。
  1. gradle/libs.versions.toml ファイルに、以下のライブラリを追加します:
    toml
  2. app/shared/build.gradle.kts に移動し、以下の依存関係を追加します:
    kotlin

    これが完了したら、Ktor Clientの薄いラッパーとして機能する TaskApi 型をクライアントに追加できます。

  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/.../ktor にある App.kt ファイルを開き、既存の App を以下の App および TaskCard コンポーザブルに置き換えます:

    kotlin

    この実装により、クライアントにいくつかの基本的な機能が備わりました。

    LaunchedEffect 型を使用することで起動時にすべてのタスクが読み込まれ、LazyColumn コンポーザブルによってユーザーはタスクをスクロールできるようになります。

    最後に、独立した TaskCard コンポーザブルが作成され、これには各 Task の詳細を表示するための Card が使用されています。タスクを削除および更新するためのボタンも追加されました。

  2. クライアントアプリケーション(例:Androidアプリ)を再起動します。 タスクをスクロールし、詳細を確認し、削除できるようになります: 改善されたUIで動作するAndroidアプリ

更新機能を追加する

クライアントを完成させるために、タスクの詳細を更新できる機能を組み込みます。

  1. app/shared/src/commonMain/.../ktor にある App.kt ファイルに移動します。
  2. 以下に示すように、UpdateTaskDialog コンポーザブルと必要なインポートを追加します:

    kotlin

    これは、ダイアログボックスで Task の詳細を表示するコンポーザブルです。description(説明)と priority(優先度)は、更新できるように TextField コンポーザブル内に配置されています。ユーザーが更新ボタンを押すと、onConfirm() コールバックが実行されます。

  3. 同じファイル内の App コンポーザブルを更新します:

    kotlin

    現在選択されているタスクを保持するための追加の状態(State)を保存しています。この値が null でない場合、UpdateTaskDialog コンポーザブルを呼び出します。その際、onConfirm() コールバックには TaskApi を使用してサーバーに POST リクエストを送信するように設定されています。

    最後に、TaskCard コンポーザブルを作成する際に、onUpdate() コールバックを使用して currentTask 状態変数を設定します。

  4. クライアントアプリケーションを再起動します。ボタンを使用して各タスクの詳細を更新できるようになります。 Androidでのタスク更新

次のステップ

この記事では、Kotlin Multiplatformアプリケーションのコンテキスト内でKtorを使用しました。これで、さまざまなプラットフォームを対象とした、複数のサービスとクライアントを含むプロジェクトを作成できるようになりました。

見てきたように、コードの重複や冗長性なしに機能を構築することが可能です。プロジェクトのすべてのレイヤーで必要とされる型は、 core マルチプラットフォームモジュール内に配置できます。サービスにのみ必要な機能は server モジュールに、クライアントにのみ必要な機能は app モジュールに配置します。

この種の本発には、必然的にクライアントとサーバーの両方の技術に関する知識が必要になります。しかし、Kotlin Multiplatform ライブラリと Compose Multiplatform を使用することで、新しく学ぶ必要がある事柄を最小限に抑えることができます。最初は単一のプラットフォームにのみ焦点を当てている場合でも、アプリケーションの需要が高まるにつれて、他のプラットフォームを簡単に追加することができます。