3/10

2026

iOSエンジニアのためのAndroid Navigation入門 — UINavigationControllerとの対比で理解する

#Android#Kotlin#Jetpack Navigation#iOS#クロスプラットフォームiOSエンジニアのためのAndroid Navigation入門 — UINavigationControllerとの対比で理解する

iOSエンジニアのためのAndroid Navigation入門

iOSでUINavigationControllerNavigationStack1を使った画面遷移に慣れているエンジニアが、Android Jetpack Navigation2を学ぶときに「これはiOSでいう何?」と感じるポイントは多い。

この記事では、Android Navigationの主要概念をiOSの対応概念と対比しながら解説する。Navigation Compose3(Jetpack Compose4向け)を中心に扱う。

概要

NavController5はAndroid Navigationの中核で、画面遷移の実行とバックスタック6の管理を担う。iOSでいうUINavigationControllerに最も近い存在だ。

AndroidiOS
クラス名NavControllerUINavigationController
Compose版NavHostControllerNavigationStack (SwiftUI)
取得方法rememberNavController()navigationController プロパティ
前画面に戻るpopBackStack()popViewController()
ルートまで戻るpopBackStack(route, inclusive)popToRootViewController()

取得方法

// Compose: NavControllerの作成と保持
@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") { HomeScreen(navController) }
        composable("detail/{id}") { DetailScreen(navController) }
    }
}

iOSのUINavigationControllerはStoryboard7かコードで生成するが、AndroidではComposable関数内でrememberNavController()を呼ぶだけでよい。

💡 iOSのself.navigationControllerに相当するのがnavController。ただしAndroidでは明示的に引数で渡すか、LocalNavControllerを使ってCompositionLocal8経由で取得する。

Route — 画面遷移先の指定方法

文字列ベースのRoute(従来方式)

Android Navigationでは遷移先を文字列で指定する。iOSのSegue9識別子に近いが、URLパスのような書式を使う。

// 画面遷移(文字列Route)
navController.navigate("detail/42")

// ルート定義
composable("detail/{id}") { backStackEntry ->
    val id = backStackEntry.arguments?.getString("id")
    DetailScreen(id = id)
}

Type-safe Route(Navigation Compose 2.8+)

Navigation Compose 2.8以降では、Kotlin Serialization10を使った型安全なRouteが導入された。文字列のtypoによるクラッシュを防げる。

// Route定義(データクラス)
@Serializable
data class DetailRoute(val id: Int)

@Serializable
object HomeRoute

// NavHost定義
NavHost(
    navController = navController,
    startDestination = HomeRoute
) {
    composable<HomeRoute> { HomeScreen(navController) }
    composable<DetailRoute> { backStackEntry ->
        val route = backStackEntry.toRoute<DetailRoute>()
        DetailScreen(id = route.id)
    }
}

// 遷移
navController.navigate(DetailRoute(id = 42))

iOS対比

AndroidiOS備考
文字列Route "detail/42"Segue identifier文字列ベース、typoに弱い
Type-safe Route DetailRoute(id=42)NavigationLink(value:) (SwiftUI)型安全
Route定義 (composable)Storyboard / Router / Coordinator11遷移先の登録

📝 iOSのCoordinatorパターンはAndroidには直接的な対応物がないが、NavGraphのネストが似た役割を果たす。画面遷移のロジックをグラフ構造として宣言的に定義する点で共通している。

NavGraph12はアプリ内の全画面遷移を定義するグラフ構造だ。どの画面からどの画面に遷移できるかを宣言的に記述する。iOSのStoryboard7に近い概念だが、コードで定義する点が異なる。

NavHost(
    navController = navController,
    startDestination = "home"  // 最初に表示する画面
) {
    composable("home") { HomeScreen(navController) }
    composable("settings") { SettingsScreen(navController) }
    composable("profile/{userId}") { ProfileScreen(navController) }
}

startDestination

startDestinationはNavGraphのエントリーポイント。iOSのStoryboardにおけるInitial View Controller13に相当する。

ネストされたサブグラフ

大規模アプリでは、機能ごとにサブグラフ(ネストされたNavGraph)を定義してモジュール化する。

NavHost(
    navController = navController,
    startDestination = "main"
) {
    // メイン画面群
    navigation(startDestination = "home", route = "main") {
        composable("home") { HomeScreen(navController) }
        composable("detail/{id}") { DetailScreen(navController) }
    }

    // 認証画面群(サブグラフ)
    navigation(startDestination = "login", route = "auth") {
        composable("login") { LoginScreen(navController) }
        composable("register") { RegisterScreen(navController) }
    }
}
AndroidiOS備考
NavGraphStoryboard画面遷移の全体定義
サブグラフ (navigation {})Storyboard Reference / 子Coordinator機能単位の分割
startDestinationInitial View Controllerエントリーポイント

popUpToでスタックを巻き戻す

popUpToは指定した画面までバックスタックを巻き戻す。iOSのpopToViewController(_:animated:)に相当する。

// "home"までスタックを巻き戻してから"settings"に遷移
navController.navigate("settings") {
    popUpTo("home") {
        inclusive = false  // "home"自体は残す
    }
}

inclusive = trueにすると、指定した画面自体もスタックから除去される。iOSのpopToRootViewController()に近い動作になる。

// ログイン後にauthグラフ全体をスタックから除去
navController.navigate("home") {
    popUpTo("auth") {
        inclusive = true  // "auth"グラフ自体も除去
    }
}

launchSingleTop

launchSingleTop = trueは、同じ画面がスタックの最上位にある場合に重複生成を防ぐ。iOSでは手動で実装する必要があるパターンだ。

navController.navigate("home") {
    launchSingleTop = true  // 同じ"home"がtopにあれば再利用
    popUpTo("home") {
        inclusive = true
        saveState = true
    }
    restoreState = true
}
AndroidiOS備考
popUpTo(route)popToViewController()指定画面まで戻る
popUpTo(route) { inclusive = true }popToRootViewController() + dismissルートごと除去
launchSingleTop手動実装が必要重複画面防止
saveState / restoreState該当なしBottomNav切替時の状態保存

popUpToinclusiveの組み合わせは最初は混乱するが、「ログイン→ホーム遷移でログイン画面を戻り先から消す」というユースケースで理解するのが一番分かりやすい。

画面間のデータ受け渡し

Route引数(型安全)

Type-safe Routeを使えば、画面間のデータ受け渡しはRouteクラスのプロパティとして自然に表現できる。

// Route定義にデータを含める
@Serializable
data class DetailRoute(val id: Int, val title: String)

// 遷移時にデータを渡す
navController.navigate(DetailRoute(id = 42, title = "Quest Name"))

// 遷移先で受け取る
composable<DetailRoute> { backStackEntry ->
    val route = backStackEntry.toRoute<DetailRoute>()
    DetailScreen(id = route.id, title = route.title)
}

SavedStateHandle — 前の画面に結果を返す

iOSのdelegateパターンや@Binding14に相当するのがSavedStateHandle15だ。画面Bから画面Aに結果を返すときに使う。

// 画面A: 結果を待ち受ける
composable("screenA") { backStackEntry ->
    val result = backStackEntry.savedStateHandle
        .get<String>("result_key")
    ScreenA(
        result = result,
        onNavigateToB = { navController.navigate("screenB") }
    )
}

// 画面B: 結果を設定して戻る
composable("screenB") {
    ScreenB(onConfirm = { value ->
        navController.previousBackStackEntry
            ?.savedStateHandle
            ?.set("result_key", value)
        navController.popBackStack()
    })
}
AndroidiOS備考
Route引数prepareForSegue / NavigationLink(value:)遷移先にデータを渡す
SavedStateHandledelegate / @Binding / onDismiss前画面に結果を返す
ViewModel16共有@EnvironmentObject画面間で状態を共有

💡 iOSでは@Bindingdelegateで前画面に結果を返すのが自然だが、AndroidではSavedStateHandleがナビゲーション結果の標準的な受け渡し方法。プロセス再生成後も値が復元される利点がある。

DeepLink対応

Android Navigationは、URLベースのDeepLink17をNavGraphに直接統合できる。

composable(
    route = "detail/{id}",
    deepLinks = listOf(
        navDeepLink {
            uriPattern = "https://example.com/detail/{id}"
        }
    )
) { backStackEntry ->
    val id = backStackEntry.arguments?.getString("id")
    DetailScreen(id = id)
}

アプリ外からのURLリンクが、NavGraph上の正しい画面に自動的にルーティングされる。バックスタックも適切に構築される(DeepLinkで直接Detail画面に飛んでも、戻るボタンでHome画面に戻れる)。

AndroidManifest.xmlへの登録

DeepLinkを有効にするには、AndroidManifest.xml18にintent-filterを追加する必要がある。

<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:pathPattern="/detail/.*" />
    </intent-filter>
</activity>

iOS対比

AndroidiOS備考
navDeepLink { uriPattern = ... }onOpenURL (SwiftUI) / application(_:open:)URLハンドリング
intent-filter (Manifest)Associated Domains + apple-app-site-association19OSへの登録
自動バックスタック構築手動実装が必要DeepLink時の戻り先

📝 iOSのUniversal Links20はサーバー側にapple-app-site-associationファイルが必要だが、AndroidのDeepLinkはManifestへの登録だけで動く(App Links21はサーバー検証が必要)。

まとめ — iOS ↔ Android 画面遷移対応表

概念AndroidiOS (UIKit)iOS (SwiftUI)
遷移コントローラNavControllerUINavigationControllerNavigationStack
遷移先の指定Route (文字列 or 型安全)Segue / pushNavigationLink(value:)
遷移グラフNavGraphStoryboardnavigationDestination
バックスタック制御popUpTopopToViewControllerNavigationPath
データ受け渡しRoute引数 / SavedStateHandleprepareForSegue / delegate@Binding / onDismiss
DeepLinkNavDeepLink + ManifestUniversal Links + AASAonOpenURL

iOSとAndroidの画面遷移は設計思想が異なるが、解決しようとしている問題は同じだ。iOSでの経験をベースにしながら、AndroidではNavGraphという「宣言的な遷移定義」が中心にあることを意識すると、学習がスムーズになる。

Footnotes

  1. NavigationStack — SwiftUI(iOS 16+)の画面遷移コンテナ。NavigationViewの後継で、値ベースのナビゲーションをサポートする。

  2. Jetpack Navigation — Android Jetpackライブラリの一部。画面遷移、バックスタック管理、DeepLink対応を統合的に提供する。

  3. Navigation Compose — Jetpack Composeと統合されたNavigation実装。Composable関数ベースで画面遷移を定義する。

  4. Jetpack Compose — Androidの宣言的UIフレームワーク。iOSのSwiftUIに相当する。Kotlin DSLでUIを記述する。

  5. NavController — Navigationライブラリの中核クラス。バックスタックの管理、画面遷移の実行、DeepLinkの処理を担当する。

  6. バックスタック — 画面遷移の履歴をスタック(LIFO)構造で管理するもの。戻るボタンを押すとスタックの最上位が除去され、前の画面に戻る。

  7. Storyboard — iOS開発におけるビジュアルな画面遷移エディタ。XML形式で画面のレイアウトと遷移(Segue)を定義する。 2

  8. CompositionLocal — Jetpack Composeの暗黙的データ伝搬メカニズム。SwiftUIの@Environmentに相当する。

  9. Segue — iOSのStoryboard上で定義する画面遷移。識別子(文字列)で遷移先を指定する。

  10. Kotlin Serialization — Kotlin公式のシリアライゼーションライブラリ。データクラスをJSON等に変換する。Navigation Compose 2.8+ではRoute定義にも使われる。

  11. Coordinator — iOS開発で画面遷移ロジックをViewControllerから分離するデザインパターン。ViewControllerの肥大化を防ぐ。

  12. NavGraph — Navigation内の全画面(destination)と遷移経路を定義するグラフ構造。startDestinationで初期画面を指定する。

  13. Initial View Controller — StoryboardのEntry Point。アプリ起動時に最初に表示されるViewControllerを示す矢印マーク。

  14. @Binding — SwiftUIのプロパティラッパー。親ビューの状態を子ビューから読み書きできるようにする双方向バインディング。

  15. SavedStateHandle — AndroidのViewModelと連携するキーバリューストア。画面間の結果受け渡しやプロセス再生成後の状態復元に使う。

  16. ViewModel — Android Architecture Componentsのクラス。画面回転やConfiguration変更を生き延びるデータホルダー。iOSのObservableObjectに近い。

  17. AndroidManifest.xml — Androidアプリの設定ファイル。パーミッション、Activity、intent-filter等をOSに宣言する。iOSのInfo.plistに相当。

  18. apple-app-site-association — Universal LinksでiOSアプリとWebドメインを関連付けるためのJSONファイル。Webサーバーのルートに配置する。