Navigation Compose を使ってみる

Jetpack Compose で画面遷移する仕組みとして [Navigation Compose](https://developer.android.com/jetpack/compose/navigation) が用意されています。
stable になるまで大きな変更が入るそうですが、[1.0.0-alpha01](https://developer.android.com/jetpack/androidx/releases/navigation#navigation_compose_version_100_2) 時点でどのように使うのか、内部でどのようなことをしているのか書いていこうと思います。

## 実装

#### セットアップ
```
dependencies {
    implementation "androidx.navigation:navigation-compose:1.0.0-alpha01"
}
```
ライブラリへの依存を追加します。

#### 画面の定義
```Screen.kt

@Composeable
fun App() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "profile") {
        composable("profile") { ProfileScreen(...) }
        composable("friends") { FriendsScreen(...) }
    }
}
```
Composable の `NavHost` と `rememberNavController` で最初の画面や遷移先の画面を設定していきます。
`NavHost` 内の `composable` で設定した文字列をキーに画面へ遷移することになります。

#### 遷移処理
```kotlin
Button(
    onClick = { 
        navController.navigate("friends") 
    }
) {
    Text(text = "Navigate next")
}
```
`NavHostController#navigate` で遷移したい画面の `composable` に定義した文字列を渡すことで画面遷移を行います。

前の画面に戻る際は `NavHostController#popBackStack` を使います。

#### 遷移時に値を渡す

```kotlin
NavHost(...) {
    ...
    composable(
        route = "friend/{friendId}",
        arguments = listOf(navArgument("friendId") { type = NavType.StringType })
    ) {...}
}
```
`NavHost` 内の `composable` の文字列に渡したい値のプレースホルダーを定義します。
そして `arguments` に渡したい値と型の定義をします。

```kotlin
composable(
    route = "friend/{friendId}",
    arguments = listOf(navArgument("friendId") { type = NavType.StringType })
) { backStackEntry ->
    Friend(navController, backStackEntry.arguments?.getString("friendId"))
}
```
`composable` の中で定義した Composable の画面で `NavBackStackEntry#arguments` から値を取り出せます。
(Parcelable や Serializable どうするんだろ…)

## Navigation Compose の実装をみてみる
内部でどのようにして画面遷移を実現しているのかライブラリのコードをみてみましょう。

```kotlin
public fun NavGraphBuilder.composable(
    route: String,
    arguments: List<NamedNavArgument> = listOf(),
    deepLinks: List<NavDeepLink> = listOf(),
    content: @Composable (NavBackStackEntry) -> Unit
) {
    addDestination(
        ComposeNavigator.Destination(provider[ComposeNavigator::class], content).apply {
            val internalRoute = createRoute(route)
            addDeepLink(internalRoute)
            val argument = navArgument(KEY_ROUTE) { defaultValue = route }
            addArgument(argument.component1(), argument.component2())
            id = internalRoute.hashCode()
            arguments.forEach { (argumentName, argument) ->
                addArgument(argumentName, argument)
            }
            deepLinks.forEach { deepLink ->
                addDeepLink(deepLink)
            }
        }
    )
}
```
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavGraphBuilder.kt

`NavHost` 内の `composable` のコードです。
`composable` に引数に渡した文字列を `createRoute` に渡して、 `"android-app://androidx.navigation.compose/$route"` というリンクを生成して DeepLink として設定しています。
値を渡す時にプレースホルダーを使った時点でなんとなく察することができそうですが、Jetpack Navigation の DeepLink の仕組みを使って Jetpack Compose の画面遷移を実現しているようです。



これまでの Jetpack Navigation の知識をそのままに Jetpack Compose でも遷移できるようになっているので、新しく知ることも少なくすみ移行もしやすそうですね。