Compose Navigation と Activity遷移
- 別Activityで表示したい画面が複数存在する
- 別Activityでも複数画面の切り替えは Compose Navigation で実装する
- 画面には引数を渡したい
ところが NavHost
の startDestination には引数込みのパスを指定できないため、特別な対応が必要。
今回は startDestination で指定した画面に引数を渡す実装を考えます
他にも解決策は考えられます
- そもそもActivityを別に分けない
- Activityに載せる画面をひとつに限定して
NavHost
を使わない - 引数をHiltで渡す
画面と引数のサンプル
以下のような2つの画面・引数を考えます。
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
で引数のデフォルト値を代わりに指定します
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型に変換して渡します。
+ @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
に直書きすると見通し悪いので、専用の関数を用意しておきます。
// 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 にデコードします。
// 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 に引数を渡すまでの一連の実装です。
遷移元
// Composable 内部を想定
val context = LocalContext.current
Intent(context, ResultActivity::class.java).apply {
putExtra("arg", Json.encodeToString(arg))
}.also(context::startActivity)
遷移先
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)
)
}
}
}
}
}
}
}