Jetpack Navigation Compose 2026年版:型安全ナビゲーションと面接対策
Navigation Compose 2.10の型安全APIを詳細解説。Kotlin Serializationによるルート定義、ネストグラフ、ディープリンク、面接頻出質問まで網羅。

Jetpack Navigation Compose 2.10は、Kotlin Serializationを活用した型安全なナビゲーションをAndroid開発に導入しています。このアプローチにより、引数の不一致によるランタイムクラッシュが排除され、大規模コードベースでのリファクタリングが大幅に簡素化されます。
Navigation Compose 2.10.0は2026年8月にリリースされ、最小SDK 24が必要です。型安全ルートは文字列ベースの宛先ではなく@Serializableデータクラスを使用し、ナビゲーションエラーをコンパイル時に検出します。
Navigation 2.10における型安全ナビゲーションのセットアップ
型安全ナビゲーションAPIにはKotlin Serializationが必要です。モジュールレベルのbuild.gradle.ktsに以下の依存関係を追加します:
plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0"
}
dependencies {
implementation("androidx.navigation:navigation-compose:2.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}宛先はシリアライズ可能な型として定義します。引数なしの画面にはobjectを、パラメータを受け取る画面にはdata classを使用します:
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
object ProductList
@Serializable
data class ProductDetail(val productId: String)
@Serializable
data class Checkout(val cartId: String, val promoCode: String? = null)コンパイラはすべてのルートクラスに@Serializableアノテーションが付与されていることを強制します。Navigation 2.9以降では、アノテーションの欠落はリントエラーを引き起こします。
シリアライズ可能ルートによるNavHostの構築
NavHostコンポーザブルはシリアライズ可能な型を直接受け入れます。各composable<T>ブロックは特定の宛先型を処理します:
@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = Home
) {
composable<Home> {
HomeScreen(
onNavigateToProducts = { navController.navigate(ProductList) }
)
}
composable<ProductList> {
ProductListScreen(
onProductClick = { productId ->
navController.navigate(ProductDetail(productId))
}
)
}
composable<ProductDetail> { backStackEntry ->
// バックスタックエントリから型付きルートを抽出
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(
productId = route.productId,
onCheckout = { cartId ->
navController.navigate(Checkout(cartId))
}
)
}
composable<Checkout> { backStackEntry ->
val route: Checkout = backStackEntry.toRoute()
CheckoutScreen(
cartId = route.cartId,
promoCode = route.promoCode
)
}
}
}toRoute<T>()拡張関数は、宛先の完全に型付けされたインスタンスを返します。データクラスのプロパティ名を変更すると、IDEリファクタリングを通じてすべての使用箇所が自動的に更新されます。
機能モジュール向けネストナビゲーショングラフ
大規模アプリケーションではネストナビゲーショングラフが有効です。各機能モジュールは独自のグラフを定義し、ルートグラフがそれらを構成します:
@Serializable
object AuthGraph // グラフ識別子
@Serializable
object Login
@Serializable
object Register
@Serializable
data class PasswordReset(val email: String)
fun NavGraphBuilder.authNavGraph(navController: NavHostController) {
navigation<AuthGraph>(startDestination = Login) {
composable<Login> {
LoginScreen(
onRegisterClick = { navController.navigate(Register) },
onForgotPassword = { email ->
navController.navigate(PasswordReset(email))
},
onLoginSuccess = {
navController.navigate(Home) {
popUpTo<AuthGraph> { inclusive = true }
}
}
)
}
composable<Register> {
RegisterScreen(
onRegistrationComplete = {
navController.navigate(Home) {
popUpTo<AuthGraph> { inclusive = true }
}
}
)
}
composable<PasswordReset> { backStackEntry ->
val route: PasswordReset = backStackEntry.toRoute()
PasswordResetScreen(email = route.email)
}
}
}popUpTo<AuthGraph>とinclusive = trueを組み合わせることで、ログイン成功後に認証フロー全体をバックスタックからクリアし、ユーザーがログイン画面に戻ることを防止します。
Androidの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
ルート引数としてのValue Class
Navigation 2.9.0-alpha03では、ルートにおけるvalue classのサポートが追加されました。このパターンはIDの型安全性を強制し、同じプリミティブ型のパラメータを混同することを防ぎます:
@JvmInline
@Serializable
value class ProductId(val value: String)
@JvmInline
@Serializable
value class UserId(val value: String)
@Serializable
data class ProductDetail(val productId: ProductId)
@Serializable
data class UserProfile(val userId: UserId)これにより、コンパイラはProductIdが期待される場所にUserIdを渡すことを防止します。これは文字列ベースのナビゲーションでよく発生するバグです。
型安全ルートによるディープリンク
ディープリンクは外部URIをシリアライズ可能なルートにマッピングします。パターンはcomposableブロックで定義します:
composable<ProductDetail>(
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/products/{productId}"
}
)
) { backStackEntry ->
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(productId = route.productId)
}{productId}プレースホルダーはProductDetailデータクラスのproductIdプロパティに直接マッピングされます。Navigationは引数を自動的に抽出してデシリアライズします。
AndroidManifest.xmlにディープリンクを登録します:
<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/products" />
</intent-filter>
</activity>Navigation Composeにおける予測バックジェスチャー
Android 14以降では、ユーザーがコミット前に前の画面をプレビューできる予測バックジェスチャーがサポートされています。Navigation Compose 2.10はSeekableTransitionStateを通じてこれと統合します:
NavHost(
navController = navController,
startDestination = Home,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End) }
) {
// destinations
}予測バックジェスチャーは、ユーザーがスワイプすると前の宛先をリアルタイムで表示します。閾値に達する前にジェスチャーを離すと、ナビゲーションがキャンセルされ現在の画面に戻ります。
Navigation Composeの面接頻出質問
シニアAndroid面接では、ナビゲーションアーキテクチャの決定についてしばしば深掘りされます。以下は経験豊富な候補者を見分ける質問です。
なぜ文字列ベースルートより型安全ナビゲーションを使うのですか?
"product/{productId}"のような文字列ベースルートは、引数のスペルミスや型の誤りでランタイムクラッシュを引き起こします。型安全ルートはこれらのエラーをコンパイル時に検出します。ルートパラメータのリファクタリングはIDEツールを通じてすべての使用箇所を更新しますが、文字列ベースルートはコードベース全体で手動の検索・置換が必要です。
大きなオブジェクトを渡さずに宛先間でデータを共有するにはどうすればよいですか?
ルートは保存インスタンス状態にシリアライズされます。商品カタログのような大きなオブジェクトを渡すとTransactionTooLargeExceptionのリスクがあります。IDを渡してリポジトリまたは共有ViewModelからデータを読み込む方式を採用してください。
データ共有のパターンにはスコープ付きViewModelを使用します:
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repository: ProductRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// SavedStateHandleから型付きルートを抽出
private val productId: String = savedStateHandle.toRoute<ProductDetail>().productId
val product = repository.getProduct(productId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
}Composeでナビゲーションをテストするにはどうすればよいですか?
ナビゲーションテストは、ユーザーアクションが正しい宛先変更をトリガーすることを検証します:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun clickingProduct_navigatesToDetail() {
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
navController.navigatorProvider.addNavigator(ComposeNavigator())
composeTestRule.setContent {
AppNavigation(navController = navController)
}
// 商品リストに遷移
composeTestRule.onNodeWithText("View Products").performClick()
// 最初の商品をクリック
composeTestRule.onNodeWithTag("product_item_0").performClick()
// 現在の宛先を検証
val currentRoute = navController.currentBackStackEntry?.toRoute<ProductDetail>()
assertThat(currentRoute).isNotNull()
}
}NavControllerが破棄された後に使用されるとどうなりますか?
Navigation 2.9.0-alpha01でこの動作が変更されました。ホストするActivityまたはFragmentが破棄された後にNavControllerを使用すると、IllegalStateExceptionがスローされるようになりました。以前のバージョンでは黙って失敗するか、未定義の動作を生じていました。この変更により、ライフサイクルのバグが開発の早い段階で表面化します。
認証状態に基づいた条件付きナビゲーションをどのように処理しますか?
一般的なパターンは認証状態を監視し、それに応じてリダイレクトします:
@Composable
fun MainScreen(
authState: AuthState,
navController: NavHostController = rememberNavController()
) {
LaunchedEffect(authState) {
when (authState) {
is AuthState.Authenticated -> {
navController.navigate(Home) {
popUpTo(0) { inclusive = true }
}
}
is AuthState.Unauthenticated -> {
navController.navigate(AuthGraph) {
popUpTo(0) { inclusive = true }
}
}
else -> { /* loading, do nothing */ }
}
}
NavHost(
navController = navController,
startDestination = if (authState is AuthState.Authenticated) Home else AuthGraph
) {
authNavGraph(navController)
composable<Home> { HomeScreen() }
}
}LaunchedEffectは認証状態の変更に反応し、popUpTo(0) { inclusive = true }でバックスタック全体をクリアして、誤ったフローへの戻りナビゲーションを防止します。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
文字列ルートから型安全ナビゲーションへの移行
既存のプロジェクトは段階的に移行できます。文字列ルートと型安全ルートは同じNavHost内で共存できます:
NavHost(
navController = navController,
startDestination = "home" // レガシー文字列ルート
) {
// レガシー文字列ベースの宛先
composable("home") {
HomeScreen(onNavigateToProduct = { id ->
navController.navigate(ProductDetail(id)) // 新しい型安全ナビゲーション
})
}
// 新しい型安全な宛先
composable<ProductDetail> { backStackEntry ->
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(productId = route.productId)
}
}このアプローチにより、ナビゲーショングラフ全体を書き換えることなく、一度に1画面ずつ移行できます。
2026年版Jetpack Navigation Composeの重要ポイント
- Navigation 2.10.0は最小SDK 24の現行安定版です
- 型安全ルートは文字列宛先の代わりに
@Serializableデータクラスを使用します - Value classはIDに追加の型安全性を提供し、パラメータの混同を防止します
- ネストナビゲーショングラフは独立したバックスタックで機能モジュールを整理します
- ディープリンクはURIパターンをルートプロパティに直接マッピングします
- 予測バックジェスチャーは標準トランジションと自動的に統合します
- ルート引数は軽量に保つべきです:IDを渡し、リポジトリからデータを読み込みます
- 文字列ルートからの移行は混合ナビゲーショングラフで段階的に行えます
- Android面接対策でこれらのパターンを深く学べます
- 関連するUIコンセプトについてはJetpack Compose面接質問も参照してください
Android のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年9月1日 更新
タグ
共有
関連記事

2026年のAndroidモジュール化:マルチモジュールアーキテクチャと面接対策
Androidアプリのマルチモジュール設計について詳しく解説。Gradleバージョンカタログ、コンベンションプラグイン、モジュール間通信パターン、そして技術面接でよく出題される質問と回答を紹介します。

Android WorkManager 2026年完全ガイド:バックグラウンドタスク、制約条件、面接対策
Android WorkManagerを使ったバックグラウンド処理の実装方法を詳しく解説。制約条件の設定、定期実行、チェーン処理、2026年の技術面接でよく問われる質問まで網羅的にカバー。

Kotlin Flow vs StateFlow vs SharedFlow:2026年のAndroid面接質問
2026年にAndroidの面接官が問う Kotlin Flow・StateFlow・SharedFlow の質問を、明快な回答・比較表・本番投入できるコードとともに解説します。