0
1

Jetpack Compose ベースの Navigation まとめ

Last updated at Posted at 2024-08-19

はじめに

  • 以前 Fragment ベースの Navigation について書いたので、 Compose ベースでの Navigation をどう実装するか調べてみました
  • ごく基本的なところをまとめた内容なので、トラブルシューティングや応用については参考にならないと思います(ごめんなさい)
  • メモ書きをそのまま起こしただけなので、文章が雑多な点はご承知おきください

インストール

下記はあくまで Navigation に関連する依存関係だけなので、 Compose 関連のものは別途追加すること

implementation("androidx.navigation:navigation-compose:$navigation_version")

基本的な使い方

大まかな手順は以下の通り

  1. NavController を取得
  2. 下記いずれかの方法で Navigation Graph を定義
    • NavHost の builder パラメータに渡すラムダで定義
    • NavController から NavGraph を生成し、それを NavHost に与える
  3. NavController.navigate を呼んで画面遷移を発生させる

NavController の取得

rememberNavController コンポーザブルで取得

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 の戻り値を NavHostgraph パラメータに渡す

@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,
)

デスティネーションと、そこへ到達するためのルートを定義

NavHostNavController.createGraphbuilder に与えるラムダで 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 関数

APIリファレンス

fun navArgument(name: String, builder: NavArgumentBuilder.() -> Unit): NamedNavArgument
  • この関数で生成される NamedNavArgument のリストを composable 関数の arguments パラメータに渡す
  • 1つの引数について、引数名とその型を定義する

NavBackStackEntry 関数

APIリファレンス

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 のバージョンが限られており、後日改めてキャッチアップすべきかと思いました
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1