JetpackComposeの「Navigation 3」ってなんだ？ その1

Jetpack Compose向けナビゲーションライブラリ「Jetpack Navigation 3」のalpha版が公式から先日リリースされました

### はじめに
この記事では「Jetpack Navigation 3」の基本的な使い方、従来の公式のNavigationとの違いについて検証します
```
dependencies {
  // 執筆バージョン
  implementation "androidx.navigation3.navigation3-runtime:1.0.0-alpha03"
  implementation "androidx.navigation3.navigation3-ui:1.0.0-alpha03"
}
```

### とりあえず基本形は？
```kotlin
// Navigation 3
@Composable
fun Main() {
  val startKey: String = remember { "HOME" }
  val backStack: SnapshotStateList<String> = remember {
    mutableStateListOf(startKey)
  }
  NavDisplay(
    backStack = backStack
  ) { key: String ->
    when (key) {
      "HOME" -> NavEntry("HOME") {
        Column {
          Text("ここはホーム画面です")
          Button(
            onClick = { backStack.add("LIBRARY") }
          ) {
            Text("クリックしてライブラリ画面に遷移する")
          }
        }
      }
      "LIBRARY" -> NavEntry("LIBRARY") {
        Text("ここはライブラリ画面です")
      }
      else -> error("不明な画面です")
    }
  }
}
```

こんな感じです
```SnapshotStateList```という```List```向け```MutableState```でバックスタックの状態をComposableのサイクルで監視し、バックスタックをリスト操作すると画面遷移します

### 従来の公式のNavigationとの違いは？

```kotlin
// 従来のNavigation
@Composable
fun Main() {
  val startKey: String = remember { "HOME" }
  val controller = rememberNavController()
  NavHost(
    navController = controller,
    startDestination = startKey
  ) {
    composable("HOME") {
      Column {
        Text("ここはホーム画面です")
        Button(
          onClick = { controller.navigate("LIBRARY") }
        ) {
          Text("クリックしてライブラリ画面に遷移する")
        }
      }
    }
    composable("LIBRARY") {
      Text("ここはライブラリ画面です")
    }
  }
}
```

大きな違いはバックスタックのリストが隠蔽されているかです
Navigation3ではバックスタックのリスト操作をアプリ開発者に委ねることとなりました

他の違いとして、この記事では説明を省きますが、アダプティブレイアウトを構築するための便利な機能「シーン」がNavigation3に備わっています

### Navigation 3 は自分でバックスタックをリスト操作する

とはいっても基本は

```kotlin
// ホーム画面に移動
backStack.add("HOME")

// ひとつ前の画面に戻る
backStack.removeAt(backStack.lastIndex)
```
のふたつだけです

このふたつを駆使しながら、Navigation3で色々と挑戦していきましょう

メインの話はここまでです

### （補足）挑戦の前に、上記の基本形を実用形に
まず、上記の基本形はバックスタックを```rememberSaveable```で状態保持しておらず、Androidライフサイクルのアクティビティの破棄によりバックスタックが初期化されてしまいます
これを修正します

#### 修正案1 Navigation 3にて用意されている```rememberNavBackStack```を使う

```kotlin
@Serializable
data object Home: NavKey
@Serializable
data object Library: NavKey

@Composable
fun Main() {
  val startKey = remember { Home }
  val backStack: SnapshotStateList<NavKey>
      = rememberNavBackStack(startKey)
  NavDisplay(
    backStack = backStack,
    entryProvider = entryProvider {
      entry<Home> { key: Home -> /* ホーム画面 */ }
      entry<Library> { key: Library -> /* ライブラリ画面 */ }
    }
  )
```
```rememberNavBackStack```は内部で```rememberSaveable```によりバックスタックを状態保持しているので、これで完了です
ついでに```entryProvider```という、従来のNavigationのように使える関数も使ってみました、お手軽です

#### 修正案2 バックスタックを```rememberSaveable```にて保存可能なオブジェクトに変換する

```kotlin
@Serializable
sealed interface Key

@Serializable
data object Home: Key
@Serializable
data object Library: Key

@Composable
fun Main() {
  val startKey = remember { Home }
  val backStack: SnapshotStateList<Key> = rememberSaveable(
    saver = Saver<SnapshotStateList<Key>, SavedState>(
      saver = { mutableStateList: SnapshotStateList<Key> ->
        encodeToSavedState(mutableStateList.toList())
      },
      restore = { savedState: SavedState ->
        decodeFromSavedState<List<Key>>(savedState)
            .toMutableStateList()
      }
    )
  ) { mutableStateListOf(startKey) }
  NavDisplay(
    backStack = backStack
  ) { key: Key ->
    when (key) {
      Home -> NavEntry(Home) {
        /* ホーム画面 */
      }
      Library -> NavEntry(Library) {
        /* ライブラリ画面 */
      }
    }
  }
}
```

```sealed interface```は基底クラスのサブクラスの型を保持したままシリアライズとデシリアライズをするので上記のように書けます
（詳しくは[kotlinx serialization の公式ドキュメント](https://github.com/Kotlin/kotlinx.serialization/blob/v1.8.1/docs/polymorphism.md#sealed-classes)を読んでください）
バックスタックを```rememberSaveable```にて保持可能なオブジェクトの```SavedState```に変換しました

またこの修正案では、```sealed interface```の最大の特徴の```when```文にて、サブクラスの一覧を静的に取得しIDEの機能で自動的に列挙できる便利な機能を活用できます
画面を作ったのに```NavDisplay```に登録し忘れたなんてミスはコンパイラが注意してくれるのでなくなります
個人的にはこちらの修正案のほうが好きです

### 総括

この記事では「Jetpack Navigation 3」の基本的な使い方、従来のNavigationとの大きな違いについて検証し執筆しました

また数日中にNavigation3に関する記事を書こうと考えてますので、よろしくお願いします

### 参考

・ Android Developers Blog : Announcing Jetpack Navigation 3

https://android-developers.googleblog.com/2025/05/announcing-jetpack-navigation-3-for-compose.html

・ AndroidX Navigation3 : Overview

https://developer.android.com/guide/navigation/navigation-3

・ AndroidX Navigation3 : リリースページ

https://developer.android.com/jetpack/androidx/releases/navigation3

・ AndroidX Navigation3 : 公式サンプル

https://github.com/android/nav3-recipes

### License
```
Copyright 2025 oikvpqya Yuya
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```