> For the complete documentation index, see [llms.txt](https://docs.wantedly.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.wantedly.dev/fields/apps/mobile-native-app/mobile-kmp.md).

# Kotlin Multiplatform (KMP)

## 導入背景と現状

KMPは2021年からWantedly アプリに本格導入され、現在はビジネスロジックの共有において中核的な役割を果たしています。 導入の経緯については、[React Nativeをやめる話とKotlin Multiplatform](https://www.wantedly.com/companies/wantedly/post_articles/282562)で詳しく解説されています。

## 現在の導入状況

KMPはWantedly アプリに導入され、iOS / Android間でビジネスロジック（Reactor実装、API通信、データベース操作など）を共有しています。 2025年12月に、それまで別々だったリポジトリを単一のモノレポ `wantedly/visit-app` に統合しました。

**リポジトリ構成**

```
visit-app/
├── shared/      # iOS / Androidで共有するKMPコード（旧 visit-app-shared）
├── iosApp/      # iOSアプリ
└── androidApp/  # Androidアプリ
```

**モノレポ化による改善**

* KMPの変更がアプリに反映されるまで20分以上かかっていたのが、即座に反映されるようになった
* 破壊的変更を同一PR内で検知できるようになった（従来は別リポジトリのCI実行まで気づけなかった）
* iOS / Androidの変更を1つのPRでアトミックに行えるようになった

モノレポ化の詳細は [Wantedly のモバイルアプリのモノレポ化について書かれた記事](https://www.wantedly.com/companies/wantedly/post_articles/1039908) を参照してください。

## KMPの方針

**Wantedly アプリ**: 積極的な導入を推進

* 新機能開発時はKMP Reactorの採用を優先
* 既存機能の段階的な移行を実施

**全社展開**: 慎重な検討段階

* 認証処理などの共通性が高い機能から実験的に導入
* 各アプリの特性を考慮した段階的な適用

## KMPのアーキテクチャ

### 設計思想

KMPでは、既存のReactorKitパターンを踏襲した独自のReactorアーキテクチャを採用しています。 これにより、iOS / Android開発者の学習コストを最小限に抑えながら、プラットフォーム間でのビジネスロジック共有を実現しています。

![KMPアーキテクチャ](/files/-MhWAONUXSOOZaWjmbS7)

### モジュール構成

**shared の主要モジュール**

* `:api`: API通信関連（REST/GraphQL）
  * `:entity`: APIエンティティ定義
  * `:graphql`: GraphQL クライアント（Apollo）
* `:core`: 共通ユーティリティ
* `:visit-app-shared`: メインモジュール（Reactor実装）
* `:testing`: テスト用ユーティリティ

### 技術スタック

* データベース: SQLDelight（型安全なSQL）
* API通信: Ktor Client + Apollo GraphQL
* 非同期処理: Kotlin Coroutines
* 依存性注入: 手動DI（DIフレームワークには依存しない）
* テスト: Kotest
* iOS連携: SKIE（Kotlin の共有コードを Swift から自然に扱えるようにする相互運用ツール）

### iOS 連携（SKIE）

Wantedly アプリでは [SKIE](https://skie.touchlab.co/) を導入し、KMP の共有コードを Swift にとって自然な形で公開しています。主に次の用途で活用しています。

* **sealed class の網羅的な switch**: Swift 側で全ケースを網羅的に分岐でき、分岐漏れをコンパイル時に検出できる
* **suspend 関数の async/await 化**: Kotlin の `suspend` 関数を Swift の async/await として自然に呼び出せる
* **デフォルト引数・enum のブリッジ**: Kotlin のデフォルト引数や enum を Swift から自然に扱える

## Reactor

### 概要

ReactorはReactorKitを参考に開発された、KMP環境での単方向データフローを実現するフレームワークです。 iOS / Android両プラットフォームで一貫したアーキテクチャパターンを提供し、予測可能な状態管理を実現します。

### データフロー

![Reactor](/files/-MhWAONVlOWyLLcfSjIg)

**基本的なデータフロー**

1. **Action**: UIからのユーザーインタラクション（タップ、入力など）
2. **Mutate**: `Action`を受けてAPI通信などの副作用を実行し、`Mutation`を生成
3. **Reduce**: `Mutation`を受けて現在の`State`を新しい`State`に変換
4. **State**: UIが監視する状態オブジェクト、変更時にUI更新をトリガー

### State の設計

State は 2 種類の情報で構成します。

* **モードに関わらず常に持つ情報**（例: 一時的に表示するエラートースト）→ **data class** のフィールドにする
* **画面のモード**（Loading / Succeeded / Error のいずれか一つ）→ **sealed class** にし、各モードに固有のデータはそのサブタイプに持たせる

shared（KMP）で定義するのは State だけです。State を UI に反映する処理は、後述のとおり iOS / Android の各プラットフォーム側が担います。

下記は、ある一覧画面（会社一覧など）を例にした最小の State 定義です。

```kotlin
// shared（KMP）で定義する State
// 画面全体の State は data class（常に存在する付随情報を持てる）
data class State(
    val state: ScreenState = ScreenState.Loading,
    val toastError: Throwable? = null, // 主状態とは独立に一時表示する付随情報
)

// 排他的な画面モードは sealed interface（和の型）で表現
sealed interface ScreenState {
    data object Loading : ScreenState

    // 成功時だけ、一覧本体とページネーションに必要な情報をまとめて持つ
    data class Succeeded(
        val items: List<String>,        // 表示するリスト本体
        val hasNextPage: Boolean,       // 次ページが存在するか
        val isNextPageLoading: Boolean, // 追加読み込み中か（末尾のスピナー表示に使う）
        val nextPageCursor: String?,    // 次ページ取得の起点（なければ null）
    ) : ScreenState

    data class Error(val cause: Throwable) : ScreenState // 失敗時だけ原因を持つ
}
```

`hasNextPage` / `isNextPageLoading` / `nextPageCursor` は成功後にしか意味を持たない情報なので、`Loading` や `Error` からは触れないよう `Succeeded` の中に閉じ込めています。追加読み込みは「新しい画面モードに遷移する」のではなく、`Succeeded` のまま `copy` でフラグを更新することで表現します。

**なぜ sealed class で分けるのか**

* **網羅性の強制**: Kotlin の `when` / Swift の `switch` が全サブタイプの分岐を要求するため、状態を追加したときの「対応漏れ」がコンパイルエラーで検出できる。
* **不正な状態アクセスの排除**: `Succeeded` だけが一覧データを、`Error` だけが原因を持つため、「ローディング中なのに一覧を参照する」といったアクセスを型レベルで防げる。
* **Declarative UI との相性**: 「状態 → UI」を宣言的な一対一マッピングとして書ける。

なお「追加読み込み中」のような成功状態の中の細かな進捗は、新たなサブタイプを増やすのではなく `Succeeded` のフィールド（フラグや cursor）として持たせることで、状態の数を最小限に保っています。

### 実装詳細

詳細な実装については以下を参照：

* [Wantedly VisitにおけるKotlin Multiplatformの導入と実装](https://www.wantedly.com/companies/wantedly/post_articles/300999)
* [wantedly/visit-app リポジトリ](https://github.com/wantedly/visit-app)（`shared/` 配下に KMP の共有コード）

## Database as Single Source of Truth

### 設計原則

KMPアーキテクチャでは、SQLDelightを使用したデータベースをSingle Source of Truthとして採用しています。 この設計により、データの一貫性を保ちながら、複数画面間でのリアルタイムな状態同期を実現しています。

### 技術実装

**SQLDelight の特徴**

* 型安全性: SQLクエリのコンパイル時検証
* マルチプラットフォーム: iOS / Android共通のDB操作
* Reactive: Kotlin Coroutines Flowによるリアクティブクエリ
* パフォーマンス: ネイティブSQLiteの性能を活用

### データフロー例：ブックマーク機能

**依存関係**

![依存の流れ](/files/-MhWAONXcMozjGj0t6Zc)

各画面のReactorは、SQLDelightのFlowを通じてデータベースの変更を監視します。 破線は、データの購読（Observe）関係を表しています。

**データストリーム**

![データの流れ](/files/-MhWAONYCpyb0efLQmNl)

1. **初期データ取得**: 各画面が必要なデータをDBから取得
2. **リアルタイム監視**: DBの変更をFlowで監視
3. **自動UI更新**: データ変更時に自動的にUI更新

**状態変更フロー**

![ブックマークのフロー](/files/-MhWAONZ56HoyHWxqcjs)

1. **ユーザーアクション**: 詳細画面でブックマークボタンをタップ
2. **API通信**: サーバーにブックマーク状態を送信
3. **DB更新**: レスポンス受信後、ローカルDBを更新
4. **自動伝播**: DB変更が全ての購読者（画面）に自動通知
5. **UI反映**: 一覧画面と詳細画面の両方で即座にUI更新

### 実装上の利点

* データ整合性: 単一のデータソースによる一貫した状態管理
* パフォーマンス: ローカルDBによる高速なデータアクセス
* オフライン対応: ネットワーク状況に依存しない基本機能
* 開発効率: プラットフォーム間でのロジック共有

### GraphQL統合

**導入背景** Wantedlyでは開発効率とパフォーマンス向上のためにGraphQLを採用しており、新しい画面は基本的にGraphQLで実装しています。 詳細は[GraphQL Gateway - アプリ向けにAPIを公開する](https://docs.wantedly.dev/fields/the-system/graphql-gateway)を参照してください。

**KMPでの実装**

* クライアント: [Apollo GraphQL](https://www.apollographql.com/) Kotlin版
* コード生成: GraphQLスキーマからKotlinコードを自動生成
* 型安全性: クエリとレスポンスの型安全な操作
* キャッシュ: Apollo Clientの自動キャッシュ機能

**開発方針**

* 新機能: GraphQLを優先的に採用
* 既存機能: REST APIから段階的に移行
* パフォーマンス: 必要なデータのみを取得（Over-fetching回避）

## 話を聞きに行きたい

* Slack: [#mobile\_chapter](https://wantedly.slack.com/archives/C014VN3KKB9)

## もっと知りたい

* [モバイルネイティブアプリについて](https://docs.wantedly.dev/fields/apps/mobile-native-app) の章（本 Handbook 内。各 OS の世代の全体像）
* [iOS の章](https://docs.wantedly.dev/fields/apps/mobile-ios) / [Android の章](https://docs.wantedly.dev/fields/apps/mobile-android)（本 Handbook 内。各プラットフォームでの利用）
* [wantedly/visit-app](https://github.com/wantedly/visit-app) リポジトリ (internal。KMP の共有コードを含む)
