Kotlin Multiplatformでフルスタックアプリケーションを構築する
Kotlin Multiplatformでフルスタックアプリケーションを構築する
コード例: full-stack-task-manager
使用されているプラグイン:
この記事では、Android、iOS、Web、デスクトップの各プラットフォームで動作し、Ktorを活用してシームレスなデータ処理を行うフルスタックアプリケーションをKotlinで開発する方法を学びます。
このチュートリアルの終わりまでに、以下のことができるようになります:
- Kotlin Multiplatformを使用してフルスタックアプリケーションを作成する。
- IntelliJ IDEAで生成されたプロジェクトの構造を理解する。
- Ktorサービスを呼び出すCompose Multiplatformクライアントを作成する。
- 設計の異なるレイヤー間で共有型を再利用する。
- マルチプラットフォームライブラリを正しく導入し、設定する。
これまでのチュートリアルでは、タスクマネージャーの例を使用して、
今回は、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を作成します。
- 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 実行構成を選択します。

- 実行 ボタン (
) をクリックして構成を実行します。
実行 ツールウィンドウに新しいタブが開きます。
ブラウザで http://0.0.0.0:8080/ にアクセスしてアプリケーションを開きます。 ブラウザにKtorからのメッセージが表示されるはずです。

プロジェクトを詳しく見る
server フォルダーは、プロジェクト内にある3つのKotlinモジュールの1つです。残りの2つは 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型では、期待宣言と実効宣言 (expected and actual declarations) を通じて、プラットフォーム固有の API を使用して現在のプラットフォームの名前を取得します。shared サブモジュールの commonMain ソースセットでは、
getPlatform()関数がexpectキーワードとともに宣言されています:kotlin次に、以下に示すように、各ターゲットプラットフォームが
getPlatform()関数のactual宣言を提供します:kotlinkotlinkotlinkotlin
クライアントアプリケーションを実行する
ターゲットの実行構成を実行することで、クライアントアプリケーションを起動できます。iOSシミュレーターでアプリケーションを実行するには、以下の手順に従ってください:
- IntelliJ IDEAで、 iosApp の実行構成とシミュレートされたデバイスを選択します。

- 実行 ボタン (
) をクリックして構成を実行します。
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 という名前の新しいファイルを作成します。
優先度を表す列挙型(enum)と、タスクを表すクラスを追加します。
Taskクラスは、kotlinx.serializationライブラリのSerializable型でアノテーションされています:kotlin
サーバーを作成する
次の段階は、タスクマネージャーのサーバー実装を作成することです。
- server/src/main/kotlin/com/example/ktor フォルダーに移動し、 model というサブパッケージを作成します。
このパッケージ内に、新しい TaskRepository.kt ファイルを作成し、リポジトリ用の以下のインターフェースを追加します:
kotlin同じパッケージ内に、以下のクラスを含む InMemoryTaskRepository.kt という新しいファイルを作成します:
kotlinserver/src/main/kotlin/.../Application.kt に移動し、既存のコードを以下の実装に置き換えます:
kotlinこの実装は以前のチュートリアルと非常によく似ていますが、簡略化のためにすべてのルーティングコードを
Application.module()関数内に配置している点が異なります。このコードを入力してインポートを追加すると、複数のコンパイルエラーが発生します。これは、Webクライアントとの対話に必要な
CORSプラグインなど、依存関係として含める必要がある複数のKtorプラグインをコードが使用しているためです。必須の依存関係: io.ktor:%artifact_name%- gradle/libs.versions.toml ファイルを開き、以下のライブラリを定義します: toml
サーバーモジュールのビルドファイル( server/build.gradle.kts )を開き、以下の依存関係を追加します:
kotlin- もう一度、メインメニューから Build | Sync Project with Gradle Files を実行します。 インポートが完了すると、
ContentNegotiation型とjson()関数のインポートが正しく機能するはずです。 - サーバーを再起動します。ブラウザからルートにアクセスできることが確認できるはずです。
クライアントを作成する
クライアントがサーバーにアクセスできるようにするには、Ktor Clientを含める必要があります。これには以下の3種類の依存関係が関係します:
- Ktor Clientのコア機能。
- ネットワーク処理を行うプラットフォーム固有のエンジン。
- コンテンツ交渉とシリアライズのサポート。
- gradle/libs.versions.toml ファイルに、以下のライブラリを追加します: toml
- app/shared/build.gradle.kts に移動し、以下の依存関係を追加します: kotlin
これが完了したら、Ktor Clientの薄いラッパーとして機能する
TaskApi型をクライアントに追加できます。 - メインメニューから Build | Sync Project with Gradle Files を選択して、ビルドファイルの変更をインポートします。
- app/shared/src/commonMain/kotlin/com/example/ktor に移動し、 network という新しいパッケージを作成します。
新しいパッケージの中に、クライアント設定用の新しい HttpClientManager.kt ファイルを作成します:
kotlin1.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 ファイルを作成します:
kotlinapp/shared/.../App.kt に移動し、コードを以下の実装に置き換えます。 これにより、
TaskApi型を使用してサーバーからタスクのリストを取得し、各タスクの名前を列(カラム)に表示します:kotlinサーバーを実行したまま、iosApp 実行構成を実行してiOSアプリケーションをテストします。
Fetch Tasks ボタンをクリックしてタスクのリストを表示します:

NOTE
このデモでは、わかりやすさのためにプロセスを簡略化しています。実際のアプリケーションでは、暗号化されていないデータをネットワーク経由で送信しないようにすることが極めて重要です。Androidプラットフォームでは、アプリケーションにネットワーク権限を明示的に与え、クリアテキストでのデータの送受信を許可する必要があります。これらの権限を有効にするには、 app/androidApp/src/main/AndroidManifest.xml を開き、以下の設定を追加します:
xmlapp.androidApp 実行構成を使用してAndroidアプリケーションを実行します。 Androidクライアントも同様に動作することが確認できるはずです:

デスクトップクライアントについては、コンテナウィンドウにサイズとタイトルを割り当てます。 app/desktopApp/src/.../main.kt ファイルを開き、
titleを変更し、stateプロパティを設定してコードを修正します:kotlinapp [hot] 🔥 実行構成を使用してデスクトップアプリケーションを実行します:

以下のいずれかの実行構成を使用して、Webクライアントを実行します:
- app [js]: Kotlin/JSアプリケーションを実行します。
- app [wasmJs]: Kotlin/Wasmアプリケーションを実行します。

UIを改善する
クライアントはサーバーと通信できるようになりましたが、まだ魅力的なUIとは言えません。
app/shared/src/commonMain/.../ktor にある App.kt ファイルを開き、既存の
Appを以下のAppおよびTaskCardコンポーザブルに置き換えます:kotlinこの実装により、クライアントにいくつかの基本的な機能が備わりました。
LaunchedEffect型を使用することで起動時にすべてのタスクが読み込まれ、LazyColumnコンポーザブルによってユーザーはタスクをスクロールできるようになります。最後に、独立した
TaskCardコンポーザブルが作成され、これには各Taskの詳細を表示するためのCardが使用されています。タスクを削除および更新するためのボタンも追加されました。クライアントアプリケーション(例:Androidアプリ)を再起動します。 タスクをスクロールし、詳細を確認し、削除できるようになります:

更新機能を追加する
クライアントを完成させるために、タスクの詳細を更新できる機能を組み込みます。
- app/shared/src/commonMain/.../ktor にある App.kt ファイルに移動します。
以下に示すように、
UpdateTaskDialogコンポーザブルと必要なインポートを追加します:kotlinこれは、ダイアログボックスで
Taskの詳細を表示するコンポーザブルです。description(説明)とpriority(優先度)は、更新できるようにTextFieldコンポーザブル内に配置されています。ユーザーが更新ボタンを押すと、onConfirm()コールバックが実行されます。同じファイル内の
Appコンポーザブルを更新します:kotlin現在選択されているタスクを保持するための追加の状態(State)を保存しています。この値が null でない場合、
UpdateTaskDialogコンポーザブルを呼び出します。その際、onConfirm()コールバックにはTaskApiを使用してサーバーに POST リクエストを送信するように設定されています。最後に、
TaskCardコンポーザブルを作成する際に、onUpdate()コールバックを使用してcurrentTask状態変数を設定します。- クライアントアプリケーションを再起動します。ボタンを使用して各タスクの詳細を更新できるようになります。

次のステップ
この記事では、Kotlin Multiplatformアプリケーションのコンテキスト内でKtorを使用しました。これで、さまざまなプラットフォームを対象とした、複数のサービスとクライアントを含むプロジェクトを作成できるようになりました。
見てきたように、コードの重複や冗長性なしに機能を構築することが可能です。プロジェクトのすべてのレイヤーで必要とされる型は、 core マルチプラットフォームモジュール内に配置できます。サービスにのみ必要な機能は server モジュールに、クライアントにのみ必要な機能は app モジュールに配置します。
この種の本発には、必然的にクライアントとサーバーの両方の技術に関する知識が必要になります。しかし、Kotlin Multiplatform ライブラリと Compose Multiplatform を使用することで、新しく学ぶ必要がある事柄を最小限に抑えることができます。最初は単一のプラットフォームにのみ焦点を当てている場合でも、アプリケーションの需要が高まるにつれて、他のプラットフォームを簡単に追加することができます。

