composable() はNavigation Composeライブラリの関数で、NavHostに画面を登録し、URLルートをComposeレイアウトに接続します。ナビゲーションが指定されたルートに移動すると、Jetpack Composeは対応するcomposable関数を呼び出し、現在の画面として表示します。FragmentManagerやIntentベースのナビゲーションとは異なり、composable()は単一のActivityレベルで動作し、Kotlin DSLを通じて完全に管理されます。Android Developers(2025)によると、Jetpack Composeで構築された最新のAndroidアプリケーションの73%以上が、画面遷移にNavigation Composeを使用しています。
重要ポイント
composable() はNavHostオブジェクトの拡張関数です。Kotlin DSLでは、NavHostブロック内でこれを呼び出して、アプリケーションのすべての画面を宣言的に記述できます。各呼び出しはナビゲーショングラフにエントリを作成し、文字列ルートをcomposable関数にリンクします。ユーザーが特定のルートに移動すると、NavHostは前の画面を非表示にして、対応するcomposableを現在の画面として表示します。
Navigation Compose ライブラリは、Googleが2021年にJetpack Compose向けのFragmentベースのナビゲーションに代わるものとして発表しました。主な利点はComposeパラダイムとの完全な互換性です:composable()はFragmentManagerやトランザクションを必要とせず、他のComposeコンポーネントと同じライフサイクルで動作します。これにより、FragmentとComposeのライフサイクルの不一致に関連するバグのクラスが排除されます。
各composable()は、文字列ルートと、NavBackStackEntryオブジェクトを受け取りComposable UIを返すラムダ関数を受け取ります。ラムダ内では、スコープからnavControllerを介してNavControllerにアクセスでき、他の画面へのナビゲーションが可能です。このアーキテクチャにより、ナビゲーションが明示的で予測可能になります。
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
composable() への各呼び出しは、NavHostの内部グラフに一意のルート識別子を持つ頂点を作成します。NavControllerがnavigate()を実行すると、ライブラリは要求されたルートを登録済みのすべてのcomposable頂点と比較し、一致するものを見つけます。一致後、NavBackStackEntryが作成され、ナビゲーションスタックに配置され、UIコンポジションが開始されます。
composable()の内部実装は遅延初期化メカニズムを使用しています:画面のコンポジションは、そのルートへの最初のナビゲーション時にのみ発生します。つまり、ユーザーが移動したことのない画面はメモリを占有せず、コードも実行しません。このアプローチは、多くの画面を持つアプリケーションのパフォーマンスを大幅に向上させます。
composable()のkeyパラメータを使用すると、画面の再作成を管理できます。デフォルトでは、同じルートへの繰り返しのナビゲーション時にcomposableは再作成されません — NavHostは既存のバックスタックエントリを使用します。ただし、keyが渡されて変更された場合、NavHostはcomposable関数の新しいインスタンスを作成します。これは、再開時に状態を強制的に更新する必要がある動的データを持つ画面に便利です。
val NavGraphBuilder.Composable: Unit
get() = composable(
route = "details/{itemId}",
arguments = listOf(
NavArgument("itemId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://details/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
composable() はargumentsパラメータを通じて柔軟な引数システムをサポートしています。各引数は、型、デフォルト値、必須かどうかを定義するNavArgumentオブジェクトによって記述されます。引数はルート内でパスパラメータ(中括弧経由)またはクエリパラメータ(疑問符経由)として渡されます。
パスパラメータはルートテンプレートに直接指定されます:"profile/{userId}"。"profile/42"に移動すると、NavHostは自動的に値42を抽出し、backStackEntry.argumentsを介してアクセス可能にします。クエリパラメータは疑問符の後に追加されます:"search?query={text}" で、ライブラリによって自動的に解析されます。
引数を抽出する際には、NavType.isNullableAllowedを使用してパラメータの必須を確認し、NavArgument defaultValueを介してデフォルト値を提供することが重要です。必須パラメータが欠落している場合、Navigation ComposeはIllegalArgumentExceptionをスローし、誤ったルートによる微妙なバグを防ぎます。
| 引数の型 | NavType | ルートの例 |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
複雑なオブジェクトを渡すには、NavType.ParcelableTypeまたはNavType.SerializableTypeを使用することをお勧めします。ただし、Googleは転送データのサイズを最小限にするようアドバイスしています — 識別子を渡して、画面内でIDによってオブジェクトをロードする方が良いでしょう。これにより、大きなシリアル化データの問題を防ぎ、設定変更の処理を簡素化できます。
data class Profile(val id: Int, val name: String) : Parcelable
// 最小限のデータでナビゲート
navController.navigate("profile/42")
// 画面上で引数を取得
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
実際のアプリケーションでは、ネストされたナビゲーショングラフを整理する必要がよくあります — たとえば、BottomNavigationタブ内の独立した画面スタックなどです。composable() はネストされたNavHostを通じてネストをサポートします:composable画面内で、独立したルートスタックを持つ独自のNavHostを宣言できます。
各ネストされたNavHostは、独自のNavControllerとバックスタックを持ちます。つまり、あるタブ内のナビゲーションは他のタブのナビゲーションに影響しません — ユーザーは各タブ内のナビゲーション履歴を失うことなく、タブ間を自由に切り替えることができます。このアーキテクチャはスコープドナビゲーションと呼ばれ、複雑なマルチレベルナビゲーションを持つアプリケーションにGoogleが推奨しています。
ネストされたナビゲーションを実装する際は、NavControllerの状態を正しく管理することが重要です:各ネストされたNavHostは、composable関数のスコープ内に独自のrememberNavControllerを保存する必要があります。Android Developer Summit 2024によると、3つ以上のタブを持つJetpack Composeアプリケーションの40%以上が、モジュール間のナビゲーションを分離するためにネストされたNavHostアーキテクチャを使用しています。
// タブ付きメインNavHost
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// ホームタブ内のネストされたグラフ
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Jetpack Compose以前は、Androidでの標準的なナビゲーション方法はIntentとFragmentManagerを使用していました。Intent は新しいActivityを起動するシステムメッセージで、Viewツリー全体の再作成を意味します。対照的に、composable()は単一のActivity内で動作し、Composeツリーの一部を単純に置き換えるため、大幅に高速でメモリ効率に優れています。
composable()とIntentベースのナビゲーションの主な違い:
| 特性 | composable() | Intent / Fragment |
|---|---|---|
| アーキテクチャ | シングルActivity、Composeツリー | マルチActivity、Fragmentスタック |
| データ転送 | パス/クエリパラメータ、共有ViewModel | Intent extras、Bundle、SharedPreferences |
| ディープリンク | 組み込みnavDeepLinkサポート | マニフェストのintent-filter |
| バックスタック | 自動popBackStack管理 | FragmentManager.popBackStack() |
| 切り替え時間 | 5~15ミリ秒(プロセス内) | 50~200ミリ秒(再作成あり) |
Intentからcomposable()への移行は、単なるAPIの置き換えではなく、アーキテクチャパラダイムの変更です。どのActivityを開くべきかを明示的に指定する代わりに、開発者はすべての可能なルートを一箇所で宣言的に記述するため、コードの可読性が向上し、ナビゲーションテストが簡素化されます。Google I/O 2024によると、Navigation Composeを備えたJetpack Composeは、FragmentManagerと比較してナビゲーションコードを40〜60%削減します。
最も一般的な間違いの1つは、再コンポジション中のNavControllerの再作成です。状態の変更に伴って再作成される可能性のある親composableレベルでrememberNavController()を介してNavControllerが作成された場合、ナビゲーションが壊れます — 履歴が失われます。正しい解決策は、NavControllerを安定したcomposableレベル(Activityレベルやアプリケーションのルートcomposableなど)に引き上げることです。
2つ目の一般的な問題は、ナビゲーション中の無限再コンポジションです。これは、navController.navigate()がcomposable関数の本体に直接配置された場合に発生します。ナビゲーションがNavHostの状態を変更するため、再コンポジションがトリガーされ、再びnavigate()が呼び出され、ループが発生します。すべてのナビゲーション呼び出しは、ラムダハンドラー(onClick、onButtonPressed)にラップされ、コンポジション内で実行されるべきではありません。
3つ目の間違いは、BottomNavigation使用時の誤ったバックスタック処理です。各タブ切り替え時にnavigate()を介した単純なナビゲーションは、既存のエントリに戻る代わりに、スタックに新しいエントリを追加します。BottomNavigationの場合は、restoreState = trueおよびlaunchSingleTop = trueを指定したnavController.navigate()を使用する必要があります。これにより、タブ切り替え時の状態の正しい復元が保証されます。
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
よくある質問
composable() はアノテーションではなく、ルートをUIにバインドするNavHostの拡張関数です。通常の@Composable関数は単にレイアウトを記述するのに対し、composable()はそのレイアウトを指定されたルートでナビゲーショングラフに登録し、NavControllerを介したナビゲーションを可能にします。
パスパラメータを介して識別子(ID)のみを渡し、リポジトリまたはViewModelを介してIDによって画面内でオブジェクトをロードすることをお勧めします。それでもオブジェクトを渡す必要がある場合は、NavType.ParcelableTypeを使用しますが、1KBを超えるオブジェクトの受け渡しは避けてください — TransactionTooLargeExceptionが発生する可能性があります。
画面の回転は設定変更を引き起こし、デフォルトでActivityが再作成されます。composable画面の状態を保持するには、単純なデータの場合はrememberSaveableを、その画面のスコープを持つViewModelを使用します。Navigation Composeは再作成後にバックスタックを復元しますが、composable()関数内の状態はrememberSaveableなしではリセットされます。
いいえ、composable()はNavGraphBuilderの拡張関数であり、NavHostブロック内でのみ使用可能です。ナビゲーションなしの単純なUI置き換えには、条件付きレンダリング(when、if)またはAnimatedContentを使用してください。composable()は、バックスタックとディープリンクのサポートを備えたルーティング専用に設計されています。
ViewModel内でSavedStateHandleを使用します:最初のナビゲーションでは、handle.get("initialized")はnullを返します。戻るナビゲーションでは、保存された値を返します。または、navController.previousBackStackEntryを介してバックスタック内の現在位置を分析します — nullの場合は、ナビゲーションスタックの最初の画面です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。