Navigation Composeが型安全になったよ

# はじめに

[Navigation 2.8.0-alpha08](https://developer.android.com/jetpack/androidx/releases/navigation?hl=ja#2.8.0-alpha08)で、Jetpack Composeでも型安全なナビゲーションが可能になりました。

https://x.com/AndroidDev/status/1786063099545468993

## Navigation Compose

ナビゲーションは以下の３つのコンポーネントで成り立っています。

- ホスト(NavHost)
  現在のデスティネーションを表示するUI要素
- グラフ(NavGraph)
  アプリ内のすべてのデスティネーションを定義するデータ構造
- コントローラ(NavController)
  デスティネーション間の移動を中央で管理するもの

型安全になることによって大きく変わるのは、デスティネーションを定義するグラフの部分です。

これは、従来のグラフの書き方です。

```kotlin
     composable(
        route = "detail/{id}",
        arguments = listOf(
            navArgument("id") {
                type = NavType.IntType
                nullable = false
            }
        )
    ) { backStackEntry ->
        DetailScreen(
            id = backStackEntry.arguments!!.getInt("id")
        )
    }
```
例として、詳細画面(DetailScreen)はInt型のIDを必要としています。詳細画面に遷移するにはこのようにします。
```kotlin
    navController.navigate("detail/1")
```
遷移先として指定できるのは文字列であるため、数値を文字列に変換する必要があります。もしここで、間違えて、数値に変換できない文字列を渡してしまうと、クラッシュします。
```kotlin
    navController.navigate("detail/hoge") // 実行時にクラッシュ!!!
```

そこで、このような拡張関数を定義すると、間違った型を渡してしまうことを防ぐことができます。
```kotlin
fun NavController.navToDetail(id: Int) {
    navigate("detail/$id")
}

navController.navToDetail(1) //OK
navController.navToDetail("hoge") // NG -- ビルドできない
```

しかし、その拡張関数を間違って定義してしまうと、ビルドは通り、実行時にクラッシュしてしまいます。
```kotlin
fun NavController.navToDetail(id: String) {
    navigate("detail/$id")
}

navController.navToDetail("hoge") // NG -- ビルドはできるが、クラッシュ！！！
```

このように、これまでのナビゲーションの型安全は不完全で、実行時にクラッシュを起こさないためには**使い方を間違えないように気をつける**しかありませんでした。できれば、間違ったコードはビルド時に失敗してほしいです。

## 型安全なナビゲーション

[Navigation 2.8.0-alpha08](https://developer.android.com/jetpack/androidx/releases/navigation?hl=ja#2.8.0-alpha08)から、[Kotlin Serialization](https://kotlinlang.org/docs/serialization.html)を使った、型安全なグラフの定義ができるようになりました。

```kotlin:build.gradle.kts
plugins {
    // Kotlin Serialization Plugin
    kotlin("plugin.serialization") version "1.9.22"
}
dependencies {
    // Navigation Compose
    implementation("androidx.navigation:navigation-compose:2.8.0-alpha08")
    // Kotlin Serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3")
}
```

```kotlin
// 引数を受け取らないデスティネーションの定義
@Serializable
object Home

// IDを引数として受け取るデスティネーションの定義
@Serializable
data class Detail(
    val id: Int
)
```
```kotlin
    NavHost(
        navController = navController,
        startDestination = Home
    ) {
        composable<Home> {
            HomeScreen(
                onClickDetail = {
                    navController.navigate(Detail(1))
                }
            )
        }
        composable<Detail> { backStackEntry ->
            val detail: Detail = backStackEntry.toRoute()
            DetailScreen(
                id = detail.id
            )
        }
    }

```

このように、`navigate`に文字列ではなくdata classを渡せるようになっているため、型を間違えることがありません。また、`detail/{id}`のようなRoute文字列を定義する必要もなくなっています。

内部的には、`com.example.myapplication.Detail/{id}`のように、指定したタイプとパラメータ名からRouteが設定されているようです。

# まとめ

[Navigation 2.8.0-alpha08](https://developer.android.com/jetpack/androidx/releases/navigation?hl=ja#2.8.0-alpha08)で安定版になったComposeのSafe Argsについて試してみました。型安全になるだけでなく、グラフの定義がシンプルになって使いやすいです。