Compose Multiplatformで画面遷移

# はじめに

[Compose Multiplatform](https://www.jetbrains.com/ja-jp/compose-multiplatform/)の勉強をしている[@uehatsu](https://x.com/uehatsu)です。今日は[以前書いた記事](https://qiita.com/uehatsu/items/9b79f628a36069e7263c)で画面遷移を3rd Party製ライブラリ([Voyager](https://voyager.adriel.cafe/))に任せていたのを、JetBrains謹製ライブラリ[navitation-compose](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-navigation-routing.html)で実装し直してみたいと思います。

# 実際の手順

## Kotlin Multiplatform Wizardを使って基本を作成

[前回の記事](https://qiita.com/uehatsu/items/9b79f628a36069e7263c)に書いたので詳細は省略します。

`Project Name`は`NavigationComposeTest`に、`Project ID`は`info.uehatsu.navigation_compose_test`にしました。

https://kmp.jetbrains.com/

## Material3に対応させる

必須では無いですが[navigation-composeのサンプル](https://github.com/JetBrains/compose-multiplatform/tree/master/examples/nav_cupcake)がMaterial3で書かれていたので、その通りにしてみます。

### composeApp/build.gradle.kts の修正

以下の修正をしたら`Sync Now`します。

```kotlin:composeApp/build.gradle.kts
// 前略

kotlin {
    // 途中略
    
    sourceSets {
        
        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
        }
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3) // ← ここを"material"から"material3"に変更
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)
            implementation(libs.androidx.lifecycle.viewmodel)
            implementation(libs.androidx.lifecycle.runtime.compose)
        }
    }
}

// 後略
```

### App.kt の修正

App.ktの修正も必要です。以下のように修正しビルド・実行します。

```kotlin:composeApp/src/commonMain/kotlin/info/uehatsu/navigation_compose_test/App.kt
package info.uehatsu.navigation_compose_test

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button // ← "material"から"material3"に修正
import androidx.compose.material3.MaterialTheme // ← "material"から"material3"に修正
import androidx.compose.material3.Text // ← "material"から"material3"に修正
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview

import navitationcomposetest.composeapp.generated.resources.Res
import navitationcomposetest.composeapp.generated.resources.compose_multiplatform

@Composable
@Preview
fun App() {
    MaterialTheme {
        var showContent by remember { mutableStateOf(false) }
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Button(onClick = { showContent = !showContent }) {
                Text("Click me!")
            }
            AnimatedVisibility(showContent) {
                val greeting = remember { Greeting().greet() }
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(painterResource(Res.drawable.compose_multiplatform), null)
                    Text("Compose: $greeting")
                }
            }
        }
    }
}
```

修正前と後で以下のような違いが出ます。今回はパーツが少ないので差がわかりづらいですが、ボタンの形と色が変わっていることがわかります。

- 修正前
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65508/2b886b11-b5d7-4aa6-8476-14f0c9f7bdc3.png" width="320" />

- 修正後
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65508/392a044e-d3ed-9337-ac52-9fd7695ee451.png" width="320" />

## navigation-composeの導入

### gradle/libs.versions.toml の修正

今回は"2.8.0-alpha10"を導入します。

```toml:gradle/libs.versions.toml
[versions]
# 省略
androidx-navigation = "2.8.0-alpha10"
# 省略

[libraries]
# 省略
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }

# 省略
```

### composeApp/build.gradle.kts の修正

composeApp/build.gradle.ktsを以下のように修正したら`Sync Now`します。

```kotlin:composeApp/build.gradle.kts
// 省略
kotlin {
    // 省略
    
    sourceSets {
        
        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
        }
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)
            implementation(libs.androidx.lifecycle.viewmodel)
            implementation(libs.androidx.lifecycle.runtime.compose)
            implementation(libs.androidx.navigation.compose) // ← 追加
        }
    }
}

// 省略
```

### TestAppScreen.kt の追加

composeApp/src/commonMain/kotlin/info/uehatsu/navigation_compose_test/TestAppScreen.kt　を追加します。

```kotlin:composeApp/src/commonMain/kotlin/info/uehatsu/navigation_compose_test/TestAppScreen.kt
package info.uehatsu.navigation_compose_test

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

enum class TestAppScreen {
    Home, Next,
}

@Composable
fun TestApp(
    navController: NavHostController = rememberNavController(),
) {
    NavHost(navController = navController, startDestination = TestAppScreen.Home.name) {
        composable(TestAppScreen.Home.name) {
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                Text("Home")

                Button(onClick = {
                    navController.navigate(TestAppScreen.Next.name)
                }) {
                    Text("Go to Next!")
                }
            }
        }
        composable(TestAppScreen.Next.name) {
            Column(
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                Text("Next")

                Button(onClick = {
                    navController.popBackStack()
                }) {
                    Text("Go back to Home!")
                }
            }
        }
    }
}
```

### App.ktの修正

composeApp/src/commonMain/kotlin/info/uehatsu/navigation_compose_test/App.kt を修正します。

```kotlin:composeApp/src/commonMain/kotlin/info/uehatsu/navigation_compose_test/App.kt
package info.uehatsu.navigation_compose_test

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
@Preview
fun App() {
    MaterialTheme {
        TestApp()
    }
}
```

上記のように修正したら実行してみます。ボタンをタップするとそれぞれHomeからNext, NextからHomeへ遷移することがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65508/df2f7864-9f07-34b9-4774-fcc8dfd7da56.gif" width="320" />

# 最後に

今回、小さなアプリとして作成しましたが、今後navigation-composeを使って自作アプリを作ろうと画策中です！