startDestinationに引数を指定できない

# Compose Navigation と Activity遷移

- 別Activityで表示したい画面が複数存在する
- 別Activityでも複数画面の切り替えは Compose Navigation で実装する
- 画面には引数を渡したい

ところが `NavHost`の startDestination には引数込みのパスを指定できないため、特別な対応が必要。

今回は startDestination で指定した画面に引数を渡す実装を考えます

[実装の詳細・完全なコードはGitHubを参照してください](https://github.com/Seo-4d696b75/compose-navigation-sample)

:::note warn
他にも解決策は考えられます
- そもそもActivityを別に分けない
- Activityに載せる画面をひとつに限定して`NavHost`を使わない
- 引数をHiltで渡す
:::

# 画面と引数のサンプル

以下のような２つの画面・引数を考えます。

```kotlin
sealed interface ResultArg {
    data class A(
        val name: String,
        val checked: Boolean,
    ) : ResultArg

    data class B(
        val name: String,
        val age: Int,
    ) : ResultArg
}

@Composable
fun ResultBScreen(
    arg: ResultArg.B,
    modifier: Modifier = Modifier,
) {
  // ... some composables ...
}

@Composable
fun ResultBScreen(
    arg: ResultArg.B,
    modifier: Modifier = Modifier,
) {
  // ... other composables ...
}
```

# 引数を渡す実装

基本的な方針は、`NavArgumentBuilder`で引数のデフォルト値を代わりに指定します

```kotlin
NavHost(
    navController = navController,
    startDestination = "result/a/{resultArgA}"
) {
    composable(
        route = "result/a/{resultArgA}",
        arguments = listOf(
            navArgument("resultArgA") {
                defaultValue = yourArg // ← 追加
            }
        ),
    ) { entry ->
        // ... your composable ...
    }
}
```

## 引数 ⇄ String型を変換する

data class は`navArgument`に指定できないため今回は kotlin-serialization でString型に変換して渡します。

```diff
+ @Serializable
  sealed interface ResultArg {
+     @Serializable
+     @SerialName("a")
      data class A(
          val name: String,
          val checked: Boolean,
      ) : ResultArg
 
+     @Serializable
+     @SerialName("b")
      data class B(
          val name: String,
          val age: Int,
      ) : ResultArg
  }
```

## defaultValueを指定する

次に、変換された引数を`navArgument`のdefaultValueに指定します。 `NavHost`に直書きすると見通し悪いので、専用の関数を用意しておきます。
```kotlin
// ResultArg.B も用意する
fun resultArgA(defaultArg: ResultArg.A? = null) = navArgument("resultArgA") {
    type = NavType.StringType
    defaultArg?.let {
        defaultValue = Uri.encode(Json.encodeToString(it))
    }
}
```

## 引数をBackStackから読み出す

`navArgument`で渡した引数を読み出し、元の data class にデコードします。

```kotlin
// ResultArg.B も用意する
val NavBackStackEntry.resultArgA: ResultArg.A?
    get() = arguments?.getString("resultArgA")?.let {
        Json.decodeFromString(Uri.decode(it))
    }

@Composable
fun `引数をBackStackから読み出す`(
    arg: ResultArg,
){
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "result/a/{resultArgA}",
    ) {
        composable(
            route = "result/a/{resultArgA}",
            arguments = listOf(resultArgA(arg as? ResultArg.A))
        ) { entry ->
            ResultAScreen(
                arg = requireNotNull(entry.resultArgA)
            )
        }
    }
}
```

## 完成
Activityの遷移から Compose Navigation に引数を渡すまでの一連の実装です。

```kotlin:遷移元
    // Composable 内部を想定
    val context = LocalContext.current
    Intent(context, ResultActivity::class.java).apply {
        putExtra("arg", Json.encodeToString(arg))
    }.also(context::startActivity)
```

```kotlin:遷移先

class ResultActivity : ComponentActivity() {

    private val arg: ResultArg by lazy {
        val str = intent.getStringExtra("arg") ?: throw IllegalArgumentException("arg not found")
        Json.decodeFromString(str)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val navController = rememberNavController()
                    val start = remember {
                        when (arg) {
                            is ResultArg.A -> "result/a/{resultArgA}"
                            is ResultArg.B -> "result/b/{resultArgB}"
                        }
                    }
                    NavHost(
                        navController = navController,
                        startDestination = start,
                    ) {
                        composable(
                            route = "result/a/{resultArgA}",
                            arguments = listOf(resultArgA(arg as? ResultArg.A))
                        ) { entry ->
                            ResultAScreen(
                                arg = requireNotNull(entry.resultArgA)
                            )
                        }
                        composable(
                            route = "result/b/{resultArgB}",
                            arguments = listOf(resultArgB(arg as? ResultArg.B))
                        ) { entry ->
                            ResultBScreen(
                                arg = requireNotNull(entry.resultArgB)
                            )
                        }
                    }
                }
            }
        }
    }
}
```