Contents
概要と前提条件(Kotlin Compose Desktop の位置づけと準備)
ここでは Compose Desktop の用途と、開発前に揃えるべき最低限の環境を説明します。業務アプリの導入判断や初期準備に使ってください。
Compose Desktop の位置づけ
Compose Multiplatform のデスクトップ実装として、Kotlin で宣言的 UI を記述できます。
Android 向け Compose とは API とライフサイクルが異なるため、コードの流用には注意が必要です。
開発前の必須・推奨環境
以下は開発を始める前に確認すべき要素です。実務ではこれらを基準に環境を固定してください。
- JDK(推奨): LTS 系 OpenJDK(例:Adoptium/Temurin の LTS)。jpackage を使う場合はその JDK に jpackage が含まれるか確認してください。
- IDE(推奨): IntelliJ IDEA の最新安定版(Community/Ultimate)。Kotlin と Compose プラグインの互換性を合わせます。
- ビルド: Gradle(Kotlin DSL 推奨)と Gradle Wrapper の利用を推奨します。org.jetbrains.compose プラグインを利用してください。
- OS/アーキテクチャ: x86_64 と Apple Silicon(arm64)両方での検証を行ってください。各 OS の署名要件を事前確認します。
- その他: Git、CI(例: GitHub Actions)、コード署名証明書(配布時)、ネイティブツールチェイン(jpackage 等)。
新規プロジェクト作成と最小サンプル(IntelliJ 起点 / Gradle 起点)
まずは最小サンプルを動かして基盤を確認します。ここでは IntelliJ での手順と、手動で Gradle(Kotlin DSL)から作る手順を示します。
IntelliJ 起点の手順
IntelliJ の New Project で Compose テンプレートが使える場合はそれを利用すると最短です。テンプレートがないときは以下の手順で手動セットアップします。
- File → New → Project を開き、Kotlin/JVM または Compose テンプレートを選びます。
- Gradle を選択し、Kotlin DSL(.kts)を選びます。
- プロジェクト作成後、Gradle タブから wrapper を確認し、存在しない場合は後述のコマンドで生成します。
- Run 構成を作成する場合は、Run → Edit Configurations → "+" → "Application" を選択し、Main class に MainKt(トップレベル main の場合)を指定します。あるいは Gradle の Tasks → application → run を使う方法が簡単です。
注意点: IntelliJ とプロジェクトで使用する JDK を一致させてください。IDE 内の Project SDK と Gradle の toolchain/JAVA_HOME を揃えるとトラブルが減ります。
Gradle (Kotlin DSL) 起点の手順
コマンドラインで最小プロジェクトを作る手順と Gradle Wrapper の作成例です。プロジェクトで使う Gradle のバージョンはプラグイン互換性に合わせて選んでください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# ディレクトリ作成 mkdir sample-app && cd sample-app # settings.gradle.kts を作成 cat > settings.gradle.kts <<'EOF' rootProject.name = "sample-app" EOF # Gradle Wrapper を作成 (ローカルに gradle がある場合) gradle wrapper --gradle-version 8.6 --distribution-type all # または Gradle が無い場合は IntelliJ の New Project で wrapper を生成してください |
build.gradle.kts の最小例(バージョンは各自で最新互換を確認してください):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
plugins { kotlin("jvm") version "<kotlin-version>" // 例: 1.9.0 (要確認) id("org.jetbrains.compose") version "<compose-plugin-version>" // ドキュメントを確認 application } repositories { mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } kotlin { jvm { withJava() // 一部のタスクやツールで必要になることがあります } } dependencies { implementation(compose.desktop.currentOs) } // application プラグインと compose プラグインで mainClass の指定が必要です。 // mainClass は Kotlin のトップレベル main の場合 "MainKt"(パッケージ付きで完全修飾)になります。 application { mainClass.set("MainKt") } compose.desktop { application { // Compose プラグイン側の mainClass 指定 mainClass = "MainKt" nativeDistributions { packageName = "sample-app" packageVersion = "0.1.0" // targetFormats の設定はプラグインバージョン依存です。公式ドキュメントを必ず参照してください。 } } } |
注記: 上記のプラグインと mainClass の扱いはバージョンにより挙動が変わります。プラグインの公式ドキュメントを確認してください(参照リンクは最後に記載。確認: 2026-05-13)。
最小サンプルの構成と実行
最小構成例と実行方法です。まずファイルを配置します。
- src/main/kotlin/Main.kt
- src/main/resources/(必要な資産)
Main.kt の例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.window.Window import androidx.compose.ui.window.application @Composable fun App() { var count by remember { mutableStateOf(0) } MaterialTheme { Button(onClick = { count++ }) { Text("Count: $count") } } } fun main() = application { Window(onCloseRequest = ::exitApplication, title = "Sample") { App() } } |
実行コマンドの例:
- ローカル(ラッパー使用): ./gradlew run
- JAR 生成: ./gradlew jar
- ネイティブパッケージ作成(プラグイン依存): ./gradlew package など(タスク名はプラグイン・バージョンで異なります。公式ドキュメントを参照してください)
代表的な初期トラブルと調査手順
ここではよくある問題と調べ方を簡潔に示します。問題発生時の第一手順として使ってください。
- 依存解決エラー: repositories と plugin バージョンを確認し、./gradlew --refresh-dependencies を試してください。
- JDK 不整合: JAVA_HOME と IntelliJ の Project SDK を合わせます。jpackage を使う場合は jpackage が存在する JDK を使用します。
- リソース未読み込み: src/main/resources 配下に置き、パッケージ経路で読み込めるか確認します。
設計と実装の実務ポイント(ステート・リソース・ネイティブ連携)
UI 設計とステート管理は保守性とパフォーマンスに直結します。ここでは現場で役立つ具体例を示します。
ステート管理とアーキテクチャ
ロジック層と UI 層は責務を分離してください。StateFlow を用いた ViewModel パターンが扱いやすいです。
- UI ローカル: remember / mutableStateOf を使用します。
- ViewModel 層: Kotlin の StateFlow / MutableStateFlow で状態を保ち、Composable 側は collectAsState で購読します。
- 再描画抑制: derivedStateOf や remember を使って高コスト計算の再実行を避けます。
ViewModel の簡単な例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update data class UiState(val count: Int = 0) class MainViewModel { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState fun increment() { _uiState.update { it.copy(count = it.count + 1) } } } |
Composable 側では collectAsState() を使って購読します。
リソース管理とネイティブ連携
画像やフォントは src/main/resources に置き、painterResource や Font で読み込みます。ファイルダイアログやシステムトレイは AWT を使うことが現実的です。
画像読み込み例:
|
1 2 3 4 5 |
import androidx.compose.ui.res.painterResource import androidx.compose.foundation.Image Image(painter = painterResource("images/logo.png"), contentDescription = "logo") |
ファイル選択の例(AWT の FileDialog を利用):
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.awt.FileDialog import java.awt.Frame import java.nio.file.Paths fun openFile(): java.nio.file.Path? { val dialog = FileDialog(Frame(), "Open", FileDialog.LOAD) dialog.isVisible = true val dir = dialog.directory ?: return null val file = dialog.file ?: return null return Paths.get(dir, file) } |
ネイティブ連携はインターフェイスで切り出しておくと可搬性が上がります。
マルチプラットフォーム戦略
ロジックは commonMain、UI は desktopMain に分けることで共有可能性を高めます。expect/actual でプラットフォーム固有処理を切り出してください。
- 共通ロジックは副作用を持たないようにする。
- プラットフォームごとの実装は actual に配置してテスト可能にする。
テストと CI(UI テスト・ヘッドレス実行・GitHub Actions サンプル)
品質を担保するため、ユニットテストと UI テストを CI に組み込みます。ヘッドレス実行や署名を自動化する実例を示します。
ユニットと UI テストの設定
ユニットテストは JUnit5 と kotlinx-coroutines-test で行います。Compose の UI テストは ui-test-junit4 等のテストアーティファクトを使います。バージョン互換性に注意し、公式ドキュメントを参照してください。
依存例(build.gradle.kts):
|
1 2 3 4 5 6 |
dependencies { testImplementation(kotlin("test")) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:<coroutines-version>") testImplementation("org.jetbrains.compose.ui:ui-test-junit4:<compose-version>") // 変更あり。要確認 } |
Compose UI テストの簡単な例:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import org.junit.Rule import org.junit.Test class MainTest { @get:Rule val composeTestRule = createComposeRule() @Test fun counterIncrements() { composeTestRule.setContent { App() } composeTestRule.onNodeWithText("Count: 0").assertExists() composeTestRule.onNodeWithText("Count: 0").performClick() composeTestRule.onNodeWithText("Count: 1").assertExists() } } |
注記: テスト用のアーティファクト名や API はバージョン依存です。プラグインのドキュメントを確認してください(参照リンク参照、確認: 2026-05-13)。
ヘッドレス実行(Xvfb)とローカル確認
Linux CI で GUI テストを走らせるには Xvfb を使います。ローカルでも同じ手順で検証できます。
- インストール: sudo apt-get install -y xvfb
- 実行例:
|
1 2 3 4 5 6 7 |
# 方法 A: Xvfb を手動で起動 Xvfb :99 -screen 0 1920x1080x24 & export DISPLAY=:99 ./gradlew test --no-daemon # 方法 B: xvfb-run を使う xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' ./gradlew test |
GitHub Actions の簡易 CI サンプル
下は Linux ジョブで Xvfb を使いテストを実行する最小例です。実運用では OS ごとにジョブを分け、成果物の署名とアップロードを追加します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
name: CI on: [push, pull_request] jobs: test-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: temurin java-version: '17' - name: Install Xvfb run: sudo apt-get update && sudo apt-get install -y xvfb - name: Run tests (headless) run: | Xvfb :99 -screen 0 1920x1080x24 & export DISPLAY=:99 ./gradlew test --no-daemon |
Windows と macOS でのビルド・署名・Notarization は後述のセキュリティと配布セクションを参照してください。
セキュリティと配布(JDK 同梱、コード署名、Notarization、CI 自動化)
JDK の同梱やコード署名、Notarization は配布における重要項目です。ここで具体的な運用例と注意点を示します。
JDK と同梱の注意点
配布に JDK を同梱する際はライセンスを確認してください。OpenJDK(Adoptium/Temurin 等)は配布可能ですが、利用条件を確認する必要があります。jpackage を使う場合は、ビルドに使う JDK に jpackage が含まれることを確認してください。
- 推奨: LTS JDK を選び、ライセンスを明記する。
- 参考: 同梱しないスリムな配布や、自動アップデートで JRE を外部に依存する方法も検討します。
Windows の署名と CI 運用例
Windows では通常 SignTool を使った署名が行われます。商用配布では EV 証明書を用いると SmartScreen の信頼性が高まります。
- PFX で署名する例:
|
1 2 3 4 5 6 7 8 9 |
# PowerShell(Windows ランナー) # 事前に PFX を Secret に保管し、ランナー上に復号・インポートしてから署名 echo "${{ secrets.WINDOWS_PFX_BASE64 }}" | base64 --decode > cert.pfx $pwd = ConvertTo-SecureString "${{ secrets.PFX_PASSWORD }}" -AsPlainText -Force Import-PfxCertificate -FilePath cert.pfx -CertStoreLocation Cert:\CurrentUser\My -Password $pwd # signtool による署名 & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a path\to\installer.exe |
- タイムスタンプサーバーを指定して、署名にタイムスタンプを付与してください。タイムスタンプにより署名の有効性が将来も保たれます。
CI の運用上の注意:
- PFX はリポジトリに置かず、CI の Secrets に格納します。
- 証明書アクセスは最小権限のジョブに限定し、マージ時のみ署名動作を許可するなどルールを設けます。
macOS の署名・Notarization と CI 運用例
macOS の配布では Developer ID による署名と Apple の Notarization(公証)が必要になることが多いです。Apple は notarytool(API キー方式)を推奨しています。
- notarytool による notarize の概略:
`bash