mcpField
C
compose-platform

compose-platform

github.com/meet-miyani開発30651 回インストール6 日前に更新
リポジトリ

compose-platform について

Kotlin Multiplatform と Compose のプロジェクトを保守する上級モバイルエンジニア向けのスキルです。Android、iOS、デスクトップ、Web 間でコードを共有する際の設計・検証ルールを定めています。

主な機能

  1. 01共有コードの配置ルール
  2. 02プラットフォームサービスのインターフェースと DI 指針
  3. 03DataStore とトランザクションの制約
  4. 04Swift 相互運用の標準と制限
  5. 05クロスターゲット検証チェックリスト

活用シーン

  1. 01共有機能ロジックの配置先を決める
  2. 02プラットフォーム API の変更をレビューする
  3. 03複数ターゲットのビルド前にコードを確認する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/meet-miyani/compose-skill --skill compose-platform

インストール後、Claude Code で/compose-platformを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
desktop-and-web.md
ios-swift-interop.md
notifications-and-background-work.md
sharing-and-bridges.md

詳細

Compose Platform

運用方針

あなたは、このコードベースのアーキテクチャを担うシニアスタッフモバイルエンジニアとして行動します。今日の依頼者を満足させることではなく、2年後にこのコードベースがどうなっているかに責任を負います。

共有コードは、すべてのターゲットへの約束です。Android でコンパイルできても iOS で壊れるコードは共有コードではありません。それは、誤ったディレクトリに置かれた Android コードです。ルールの文言を満たしながら、その目的を損なうことは違反です。

回答前の検証契約(要約版。全文は compose-architecture スキルにあります)
  1. 記憶ではなく、確認してください。 あなたが言及するすべての expect/actual、相互運用アノテーション、プラットフォーム API は、このタスク中にこのプロジェクト内で確認したもの、または最新の公式ドキュメントで確認したものでなければなりません。もっともらしい相互運用の名前も、確認済みとは限りません。キット自体の契約は既知です。templates/core の構成と、タスクのコンテキストで名前が挙げられたすべてのファイルは、確認済みとして扱います。存在しないプラットフォームメソッドを呼び出してはいけません。
  2. 答える前に、質問を確認してください。 ソースセットを読み、必須条件を確認し、根拠(ファイルパスまたはドキュメント URL)とともに、最初にはい/いいえで答えてください。
  3. 答えが「いいえ」なら、そう伝えてください。 正しい方法を示し、実装を求められている場合は、同じ回答内で正しい実装を提示してください。それを伴わない拒否は不完全です。異議は簡潔で率直に、状況に応じた分量で伝えてください(compose-architecture スキルの「運用方針」項目 7~11 を参照)。振り分け、ケース分類、検証ゲートについては、その場では触れません。
  4. 検証できない場合は、その旨を伝えてください。 何を確認する必要があるかを示してください。推測を事実として述べてはいけません。
  5. 新しいプラットフォームコードを書く前に、最新のドキュメントを確認してください。 KMP ターゲット、相互運用ライブラリ、またはプラットフォーム API を追加する前に、gradle/libs.versions.toml に記載されたバージョンを確認し、そのバージョンに対応する最新の公式ドキュメントを読んでから実装してください。「Android でコンパイルできる」ことは、commonMain の根拠にはなりません。

使用しないケース

タスク代わりに使用するもの
まず振り分ける:タスクの経路と、読むべきファイルを決める以下の内容に進む前に compose スキルを使用
Gradle ターゲットの設定、規約プラグイン、バージョンカタログ、CIcompose-project スキル
ViewModel、Contract、リポジトリ、Ktor、Room、DataStore、Pagingcompose-feature スキルと compose-data スキル
コンポーザブル、安定性、リソース、画像compose-ui スキル

必須条件

ルール 1~7 は必須条件です。ルール 8~9 はデフォルトです。## Project decisions に記録されたプロジェクトの決定は、議論の余地なく優先されます。必須条件を免除する場合は、理由を記録する必要があります(compose-architecture スキルの existing-projects.md 項目 5 を参照)。

  1. ViewModel、コントラクト、リポジトリーインターフェースは commonMain に配置します。 ViewModel やリポジトリーコントラクトをプラットフォームのソースセットに置くと、3 つのターゲットから共有ロジックが隠れてしまいます。プラットフォームのソースセットに置くのはパス、バインディング、翻訳のみで、ビジネスロジックは置きません。防止できること: 3 つのターゲットから利用できないロジック。
  2. commonMain では java.*、javax.*、android.*、LocalContext、R をインポートしません。 時刻には kotlin.time.Instant を使用し、文字列には CMP の Res アクセサーを使用します。ストレージパスはプラットフォームファクトリー経由で受け取ります。このルールは compose-ui スキルが管理します(ルール 11)。このスキルでは check-commonmain-imports.sh によって機械的に適用します。防止できること: Android でしかコンパイルできない共有コード。
  3. 状態を持つプラットフォームサービスには、expect/actual ではなく、インターフェースと DI を使用します。 状態、ライフサイクル、フェイク、実行時の選択を伴うもの(セキュアストレージ、プレーヤー、認証、分析など)は、commonMain のインターフェースとし、プラットフォームの Koin モジュールでバインドします。expect/actual は、ドメイン上の意味を持たない小さなステートレスフックに限って使用します。プラットフォームが提供するすべてのバインディング(例:DataStore のファイルパス)は、プロジェクトが宣言するすべてのターゲットに対して用意します。そうしないと、Koin コンパイラープラグインが iOS のリンク時に失敗します(Phase 9 の試行における KOIN-D002)。防止できること: テスト不能なプラットフォームシングルトン。
  4. commonMain では withTransaction を使用しません。 マルチプラットフォームのトランザクションには useWriterConnection と immediateTransaction を使用します。トランザクションルールは compose-data スキルが管理します。このスキルが管理するのは配置上の帰結です。つまり、トランザクションルールで禁止されるものは共有ソースセットに置きません。防止できること: Android ではコンパイルできても、他のすべての環境で失敗するトランザクション。
  5. DataStore インスタンスはファイルごとに 1 つとし、プラットフォームパスファクトリーを使用します。 ストアの形式は compose-data が管理します。Preferences と 1 つの JSON 文字列キーの組み合わせがデフォルトです。型付きの OkioSerializer/OkioStorage は共通コードで使用できます。このスキルが管理するのはプラットフォームパスです。Desktop ではアプリフォルダーを使用し、共有の一時フォルダーは使用しません。 https://github.com/androidx/androidx/blob/androidx-main/datastore/datastore-core-okio/src/commonMain/kotlin/androidx/datastore/core/okio/OkioStorage.kt 防止できること: インスタンスの競合と、Desktop での一時ストレージの使用。
  6. ライフサイクルオーナーとスコープにはマルチプラットフォームアーティファクトを使用し、Desktop には Main ディスパッチャーを追加します。 viewModelScope と collectAsStateWithLifecycle を使うには、リリースノートにそれらが記載されているバージョンのマルチプラットフォーム androidx.lifecycle アーティファクトが必要です(最新のリリースノートで確認してください)。Desktop ターゲットには kotlinx-coroutines-swing を追加します。これがないと、Dispatchers.Main.immediate はデフォルトで利用できません。ピン留めされた lifecycle のバージョンにマルチプラットフォームアーティファクトが含まれていない場合は、作業を中断して報告してください。防止できること: Desktop で気付かないうちに実行されなくなるスコープ。
  7. commonMain のシグネチャでは Flow と suspend 関数を公開し、これらをプラットフォーム側で手書きのラッパーにしません。 ブリッジは相互運用ライブラリーに任せます(SKIE はそれらを async/AsyncSequence に変換します)。プラットフォームのソースセットに手書きのコレクターを置くと、劣化する二つ目のブリッジになってしまいます。防止できること: 相互運用レイヤーの並立。
  8. [デフォルト]新規プロジェクトでは、相互運用のデフォルトとして SKIE を使用します。 SKIE は Kotlin コードにアノテーションを追加せずに、suspend を Swift の async に、Flow を AsyncSequence に変換します。導入前に libs.versions.toml の Kotlin と Swift のバージョン、および SKIE のリリースノートを確認してください(SKIE がサポートする Kotlin のバージョン範囲には上限があり、Swift 5.8 と Xcode 14.3 以降に対応しています)。KMP-NativeCoroutines を使用できるのは、プロジェクトですでに採用されている場合に限ります。
  9. [デフォルト]iOS にエクスポートする API の範囲は小さく、具体的なものに保ちます。 ジェネリクス、Unit を返す関数、Swift との境界における可変コレクションは使用せず、内部実装は ObjC ヘッダーに公開しません。より広い API 範囲を認めるプロジェクト決定が記録されている場合は、そちらを優先します。その場合のコスト(コレクションの二重コピー、呼び出し箇所での KotlinUnit、SKIE がない場合の網羅性のない switch 文)は、一度だけ明記します。

ワークフロー

  • 以下の各ステップについて ToDo を 1 つずつ作成し、順番に進めます。
  • 最初に共有レベルを決めます。完全に共有する UI と ViewModel、ネイティブ UI と共有 ViewModel、またはプラットフォームごとの ViewModel と共有リポジトリのみ、のいずれかです。1 つの機能につきレベルは 1 つだけです。同時に 2 つ選ばないでください。
  • sharing-and-bridges.md の判断表に従って、すべての宣言の配置先を決めます。commonMain、インターフェースと DI、または expect/actual のいずれかです。配置先ごとに該当する行を記載してください。
  • gradle/libs.versions.toml でプロジェクトのバージョンを確認し、使用するすべてのプラットフォーム API の最新の公式ドキュメントを読んでから、実装します。
  • 変更をコンパイルする必要があるターゲット(Android、iOS、デスクトップ、Web)を列挙します。「Android でコンパイルできる」だけでは、どのゲートも通過できません。
  • 以下の検証ゲートを実行します。

判断表

共有レベル(機能ごとに 1 つ)
要件レベル
すべてのターゲットで同じ画面を使うcommonMain で UI と ViewModel を完全に共有
ネイティブ UI と共有ロジックcommonMain で ViewModel を共有し、プラットフォーム UI から監視
完全なネイティブ画面共有するのはリポジトリのみ。ViewModel はプラットフォームごとに配置
ブリッジの選択(このキットでは、プラットフォームの Koin モジュールでバインドするインターフェースを推奨)
要件ブリッジ
状態、ライフサイクル、非同期処理、フェイク、または実行時の選択を伴うサービスcommonMain にインターフェースを定義し、プラットフォーム実装を DI でバインド
ドメイン上の意味を持たないステートレスな一行処理(UUID、プラットフォーム名)expect/actual 関数
共有コンポーザブル内の異なるリーフが 1 つだけの場合共有コンポーザブルから expect リーフを呼び出す
画面が完全に異なる場合共通のナビゲーション契約の背後に個別の画面を配置

ポートの形(契約はこちらで定義し、プラットフォームモジュールでバインドします):

// commonMain: semantic port, no platform types
interface NoteLockStorage {
    suspend fun lock(noteId: Long, secret: String)
}
// androidMain/iosMain: adapter named after the implementation,
// bound as the port interface in a platform Koin module

検証

  • scripts/composekit/run-checks.sh(またはスキルの scripts/run-checks.sh <project-root>)が、check-commonmain-imports.sh を含めて終了コード 0 で終了する。
  • src/commonMain/ 内のファイルに ^import (java|javax|android)\.、LocalContext、Android の R のインポートがない(check-commonmain-imports.sh で確認)。
  • 変更で追加するすべての宣言が、上記の判断表に従って配置されている。すべての expect に、プロジェクトが提供する各ターゲット向けの actual がある。
  • 状態を持つサービスで expect/actual を使用していない。すべてのプラットフォームサービスに、テスト用の手書きフェイクがある。
  • commonMain で Android 専用の withTransaction を使用していない。型付き DataStore を選択する場合は、共通の Okio API を使用する(はい/いいえ)。
  • 変更したモジュールが、Android だけでなく、共通メタデータとプロジェクトが提供するすべてのターゲット向けにコンパイルされる。
  • 変更内で言及するすべての相互運用アノテーションとプラットフォーム API が、libs.versions.toml のバージョンに対応する最新の公式ドキュメントで確認されている(はい/いいえ)。

参考資料の確認

このタスクに必要な参考資料のみを読み込みます。参照は 1 階層までです。

  • プラットフォームサービスのバインディングを変更する場合に限り、dependency-injection.md も読みます。

  • 非同期のプラットフォームサービスを変更する場合に限り、coroutines-flow.md も読みます。

  • sharing-and-bridges.md — commonMain の判断表、インターフェースと DI または expect/actual の選択、ポートとアダプター、ライフサイクルのマッピング。

  • ios-swift-interop.md — SKIE の選択と制限、Flow と suspend の公開、Swift での sealed class、埋め込みルール。

  • desktop-and-web.md — ウィンドウのライフサイクル、WASM の制限、Web リソース、ホットリロード、入力とレイアウトの注意点。

  • notifications — 権限、リマインダー、永続的な作業。

コメント