はじめに
- 以前 Fragment ベースの Navigation について書いたので、 Compose ベースでの Navigation をどう実装するか調べてみました
- ごく基本的なところをまとめた内容なので、トラブルシューティングや応用については参考にならないと思います(ごめんなさい)
- メモ書きをそのまま起こしただけなので、文章が雑多な点はご承知おきください
インストール
下記はあくまで Navigation に関連する依存関係だけなので、 Compose 関連のものは別途追加すること
implementation("androidx.navigation:navigation-compose:$navigation_version")
基本的な使い方
大まかな手順は以下の通り
-
NavController
を取得 - 下記いずれかの方法で Navigation Graph を定義
-
NavHost
の builder パラメータに渡すラムダで定義 -
NavController
からNavGraph
を生成し、それをNavHost
に与える
-
- NavController.navigate を呼んで画面遷移を発生させる
NavController の取得
rememberNavController
コンポーザブルで取得
@Composable
fun rememberNavController(vararg navigators: Navigator<NavDestination>): NavHostController
Navigation Graph の定義
NavHost コンポーザブルで定義する場合
NavHost の builder パラメータに渡すラムダで定義
@Composable
fun NavHost(
navController: NavHostController,
startDestination: Any,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
route: KClass<*>? = null,
typeMap: Map<KType, NavType<*>> = emptyMap(),
enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
fadeIn(animationSpec = tween(700))
},
exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
fadeOut(animationSpec = tween(700))
},
popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = enterTransition,
popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = exitTransition,
sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform)? = null,
builder: NavGraphBuilder.() -> Unit
): Unit
-
builder
に渡すラムダの中で Navigation Graph を定義する - 最終的に
NavController.createGraph
が呼び出されて Navigation Graph が生成されるとのこと- 要はどちらにせよ、次項「NavController の拡張関数 createGraph で定義する場合」の内容と同じことが行われる
NavController の拡張関数 createGraph で定義する場合
まず NavController.createGraph で Navigation Graph を定義
inline fun NavController.createGraph(
startDestination: String,
route: String? = null,
builder: NavGraphBuilder.() -> Unit
): NavGraph
次に NavController.createGraph
の戻り値を NavHost の graph
パラメータに渡す
@Composable
fun NavHost(
navController: NavHostController,
graph: NavGraph,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
fadeIn(animationSpec = tween(700))
},
exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
fadeOut(animationSpec = tween(700))
},
popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = enterTransition,
popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = exitTransition,
sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform)? = null
): Unit
サンプルコード (Navigation Graph の定義)
NavHost で Navigation Graph を定義する場合
NavHost(
navController = navController,
startDestination = "start_screen",
modifier = modifier,
) {
composable(route = "start_screen") {
StartComposable()
}
composable(route = "route1") {
SomeComposable1()
}
composable(route = "route2") {
SomeComposable2()
}
...
}
NavController.createGraph で Navigation Graph を定義してから NavHost に与える場合
val graph = navController.createGraph(
startDestination = "start_screen",
) {
composable(route = "start_screen") {
StartComposable()
}
composable(route = "route1") {
SomeComposable1()
}
composable(route = "route2") {
SomeComposable2()
}
...
}
NavHost(
navController = navController,
graph = graph,
modifier = modifier,
)
デスティネーションと、そこへ到達するためのルートを定義
NavHost
や NavController.createGraph
の builder
に与えるラムダで NavGraphBuilder.composable
を呼び出して、個々のデスティネーションとルートを定義する
NavGraphBuilder.composable
1回の composable
呼び出しごとに1つのデスティネーション&ルートを定義する
APIリファレンス (オーバーロードあり)
fun NavGraphBuilder.composable(
route: String,
arguments: List<NamedNavArgument> = emptyList(),
deepLinks: List<NavDeepLink> = emptyList(),
enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition)? = null,
exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition)? = null,
popEnterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition)? = enterTransition,
popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition)? = exitTransition,
sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform)? = null,
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
): Unit
- 遷移先を描画するコンポーザブルの呼び出しは
content
で行う -
route
にセットした文字列を引数にしてNavController.navigate
が実行されるとcontent
が呼び出される。この中で遷移先のデスティネーションをコンポーズする-
route
としてnavigate
に渡せるのは文字列だけではないが、ここでは割愛
-
- 遷移元から遷移先へ引数を渡す場合は以下の実装が必要
-
route
には"route_name/{args_name}"
というように、『ルート名+引数名を {} で囲んだ文字列』を指定 -
arguments
にはnavArgument
関数(下記)を利用して、引数名と引数のデータ型をリストで定義
-
- 実際に遷移先に渡される引数は、
content
の中でNavBackStackEntry.arguments
を介して受け取ることができる
画面遷移時に引数を渡す
引数の渡し方は他にもあるが、ここではルートに "route_name/{args_name}"
を渡すことでやり取りする方法についてのみ記載
navArgument 関数
fun navArgument(name: String, builder: NavArgumentBuilder.() -> Unit): NamedNavArgument
- この関数で生成される
NamedNavArgument
のリストをcomposable
関数のarguments
パラメータに渡す - 1つの引数について、引数名とその型を定義する
NavBackStackEntry 関数
class NavBackStackEntry : LifecycleOwner, ViewModelStoreOwner, HasDefaultViewModelProviderFactory
-
composable
関数のcontent
ラムダに渡ってくる - 遷移先に渡す引数は NavBackStackEntry.arguments を介して取得する
ディープリンクの定義
navDeepLink 関数
APIリファレンス (オーバーロードあり)
fun navDeepLink(deepLinkBuilder: NavDeepLinkDslBuilder.() -> Unit): NavDeepLink
- この関数で生成される
NavDeepLink
のリストを、composable
関数のdeeplinks
パラメータに渡す - デスティネーションに遷移させるためのディープリンクを1個定義する
- 引数をつける場合、
deepLinkBuilder
で呼び出すuriPattern
には『スキーマ+ホスト名+引数名を {} で囲んだ文字列』を渡す-
composable
関数のroute
パラメータに渡す文字列と同じルール - スキーマを “sample” に、デスティネーションを “destination1” に、引数名を “param_name” とするなら
sample://destination1/{param_name}
となる
-
サンプルコード (様々な種類のルートを定義)
// NavController を取得
val navController = rememberNavController()
// NavHost を設置
// - startDestination には起点となるルートを指定
// - 後置ラムダが NavGraph で、 composable 関数に route と遷移先とする Composable の呼び出しを定義する
NavHost(
navController = navController,
startDestination = "first_page",
modifier = Modifier,
) {
// 引数を取らない場合
// シンプルにルートに対応する Composable を呼び出す
composable(route = "first_page") {
FirstPageComposable()
}
// 引数を取る場合
// ルートは "route_name/{args_name}" というように「ルート名+引数名を{}で囲んだ文字列」で定義する
// 引数とその型は navArguments 関数で定義したものを、 arguments パラメータにリストで与える
// - 第1引数に引数名を、第2引数にラムダで引数の型をセット
// 引数を取るディープリンクは navDeepLinks 関数で定義したものを deeplinks パラメータにリストで与える
composable(
route = "second_page/{param_name}",
arguments = listOf(
navArgument("param_name") { type = NavType.StringType }
),
deepLinks = listOf(
navDeepLink { uriPattern = "sample://destination1/{param_name}" },
),
) { navBackStackEntry ->
// Nav引数の取得
val args = navBackStackEntry.arguments?.getString("param_name")
ThirdPageComposable(arguments = args)
}
}
おわりに
-
Fragment
ベースの Navigation で Fragment to Fragment の遷移でしたが、 Compose ベースの Navigation は、画面の再描画に近いイメージを持ちました- 従来の画面遷移と同じ感覚で使おうとしたら痛い目を見そうな気がします
- Compose 全般に言えることかも知れませんが、その気になれば1つの Activity に全ての画面遷移を書けなくもないので、 Fragment の使い分けをどうしていくか考える必要があるなという感想です
- Serializable なオブジェクトを使って、型安全性を担保しつつデスティネーション間で値をやり取りする方法もあるようですが、使用できる Navigation のバージョンが限られており、後日改めてキャッチアップすべきかと思いました