Router(ルーター)— アプリケーションの画面間のナビゲーションロジックを集中化するアーキテクチャパターン。Routerは、イベント発生時にどこへどのように遷移するかを決定します。iOSではRouterはUINavigationControllerとsegueを抽象化し、AndroidではFragmentManagerとJetpack NavigationのNavControllerを抽象化します。Routerは独立したパターンとして、またはCoordinator(Router-per-Coordinator)やVIPER(Router-per-module)のコンポーネントとして機能できます。詳細はAndroid Navigation Component Guideをご覧ください。
重要ポイント
Router — 画面間の遷移ロジックをカプセル化するパターン。基本的な考え方:ViewController(またはPresenter/ViewModel)は直接navigationController.pushViewControllerを呼び出さず、遷移の意図をRouterに通知します。RouterはどのViewControllerを作成し、どのように表示するかを決定します。Routerは単純(ナビゲーションメソッドのセット)にも複雑(URLスキームルーティング、deep links、プッシュ通知付き)にもなります。
Routerの3つのアプローチ — サービスとしてのRouter(グローバルNavigatorシングルトン)、モジュールコンポーネントとしてのRouter(VIPERアプローチ)、プロトコルとしてのRouter(Coordinatorの抽象化)。グローバルRouter — シンプルさ(アプリ全体で1インスタンス)— 小規模プロジェクトで人気。モジュラーRouter — 各機能に独自のRouter — VIPERとClean Architectureの標準。プロトコルRouter — Coordinatorで使用:push/present/pop/dismissメソッドを持つRouterProtocol。
| アプローチ | 構造 | 使用するタイミング |
|---|---|---|
| グローバルNavigator | ナビゲーションメソッドを持つシングルトン | 小規模プロジェクト、プロトタイプ |
| モジュラーRouter | 各モジュール内のRouter | VIPER、Clean Architecture |
| プロトコルRouter | Coordinator用のプロトコル+実装 | MVVM-C、Coordinatorパターン |
パターンの歴史 — RouterはWebフレームワーク(URL Router — Rails Routes、Express.js)で登場し、モバイル開発に適応されました。iOSではVIPER(2015-2017)の台頭とともにRouterが普及しました。AndroidではRouterはJetpack Navigation(2018)の一部であり、Googleの標準ナビゲーションソリューションです。現在、Routerはモジュラーモバイルアプリケーションアーキテクチャの不可欠な部分です。
iOS Router — さまざまなナビゲーションタイプのメソッドを持つプロトコル。基本的な実装はUINavigationControllerをラップします。Routerはモーダル表示(present/dismiss)、ナビゲーションスタックでのpush/pop、Split View ControllerのshowDetail、フルスクリーン表示(iOS 13+)を処理できます。RouterはViewControllerを作成せず、準備済みのインスタンスを受け取って表示します。ViewControllerの作成はファクトリ(Assembly、DIコンテナ)またはCoordinatorが行います。
// iOS用RouterProtocol
protocol RouterProtocol {
func push(_ viewController: UIViewController, animated: Bool)
func pop(animated: Bool)
func popToRoot(animated: Bool)
func present(_ viewController: UIViewController, animated: Bool)
func dismiss(animated: Bool)
func setViewControllers(_: [UIViewController], animated: Bool)
}
// UINavigationControllerを使用したRouter実装
class NavigationRouter: RouterProtocol {
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func push(_ vc: UIViewController, animated: Bool) {
if navigationController.presentedViewController {
navigationController.dismiss(animated: false)
}
navigationController.pushViewController(vc, animated: animated)
}
func pop(animated: Bool) {
navigationController.popViewController(animated: animated)
}
func popToRoot(animated: Bool) {
navigationController.popToRootViewController(animated: animated)
}
func present(_ vc: UIViewController, animated: Bool) {
navigationController.present(vc, animated: animated)
}
func dismiss(animated: Bool) {
navigationController.dismiss(animated: animated)
}
func setViewControllers(_ vcs: [UIViewController], animated: Bool) {
navigationController.setViewControllers(vcs, animated: animated)
}
}
// Assembly — Routerを使用したモジュール作成
protocol ProfileModuleFactory {
func makeProfileView() -> UIViewController
}
VIPERのRouter — 各VIPERモジュールには独自のRouterがあり、現在のモジュールからどの画面を開くかを認識しています。Routerはファクトリを通じて次のモジュールのViewControllerを作成し、制御を渡します。VIPERのRouterは、import UIKitを含む唯一のコンポーネントです(Viewを除く)。これによりPresenterとInteractorのテストが簡素化されます:UIKitに依存しません。
Jetpack Navigation Component — Androidの標準ルーターで、2018年からGoogleが推奨しています。ナビゲーションはXMLグラフ(nav_graph.xml)で定義されます:ノード(fragments、activities、destinations)とエッジ(actions)。NavControllerが遷移を管理します:FragmentManager.replace/addを呼び出し、バックスタックを管理します。Navigation Componentはdeep links、引数、遷移アニメーション、type-safeなデータ受け渡しのためのSafe Argsをサポートしています。
// nav_graph.xml — ナビゲーショングラフ
<!-- @xml/nav_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment android:id="@+id/loginFragment"
android:name=".ui.login.LoginFragment">
<action android:id="@+id/to_home"
app:destination="@id/homeFragment" />
</fragment>
<fragment android:id="@+id/homeFragment"
android:name=".ui.home.HomeFragment">
<argument android:name="userId"
android:defaultValue="0"
app:argType="integer" />
<deepLink app:uri="myapp://home/{userId}" />
</fragment>
</navigation>
// Kotlin — NavControllerを使用したナビゲーション
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.loginButton.setOnClickListener {
val action = LoginFragmentDirections.toHome(userId = 42)
findNavController().navigate(action)
}
}
}
// Safe Args — 型安全な引数
class HomeFragmentArgs : NavArgs {
val userId: Int get() = arguments?.getInt("userId") ?: 0
}
// NavigationUI — Toolbar、BottomNav、Drawerとの統合
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHost.navController
NavigationUI.setupActionBarWithNavController(this, navController)
}
}
Android NavigationのDeep links — Navigation Componentは明示的なdeep links(PendingIntent + NavDeepLinkBuilder)と暗黙的なdeep links(マニフェストのintent-filter経由)をサポートしています。deep linkはnav_graphの任意のノードに遷移できます。プッシュ通知の処理:NotificationがNavDeepLinkBuilderでPendingIntentを作成し、バックスタックを復元して目的の画面を開きます。Navigation ComponentはUp/Backボタンを自動的に処理し、ユーザーを前の画面に戻します。
URL Routing — URLスキーム、deep links、universal linksに基づくルーティング。アプリはスキーム(myapp://profile/42)またはuniversal link(https://example.com/profile/42)を登録します。RouterはURLを解析し、パラメータ(userId=42)を抽出して、対応する画面を開きます。URL Routingにより、アプリを外部から開くことができます:Webサイトリンク、メール、プッシュ通知、QRコードから。iOSはNSUserActivity + universal linkを使用し、Androidはintent-filter + deep linkを使用します。
// URL Router — deep link解析とナビゲーション
protocol URLRoute {
var pattern: String { get }
func navigate(parameters: [String: String], router: RouterProtocol)
}
struct ProfileRoute: URLRoute {
let pattern = "myapp://profile/{userId}"
func navigate(parameters: [String: String], router: RouterProtocol) {
guard let userId = parameters["userId"] else { return }
let profileVC = ProfileViewController(userId: userId)
router.push(profileVC, animated: true)
}
}
// URL Router — URLとルートのマッピング
class URLRouter {
private var routes: [URLRoute] = []
func handle(_ url: URL, router: RouterProtocol) {
for route in routes {
if let params = matchPattern(route.pattern, url: url) {
route.navigate(parameters: params, router: router)
return
}
}
}
}
実際のDeep linkルーティング — アプリは3つの状態でdeep linksを処理する必要があります:未起動(deep linkでの起動)、バックグラウンド(バックグラウンドからの復元)、アクティブ(onNewIntent/SceneDelegate)。URL Routerはバックスタックを正しく復元する必要があります:プロフィール画面にdeep linkする場合、ユーザーが戻るボタンを押して前の画面に戻れるようにする必要があります。iOSとAndroidではバックスタックの処理が異なるため、Routerはプラットフォーム固有の特性を考慮する必要があります。
Router vs Coordinator — Routerは「画面をどのように表示するか?」(push/present)に答えます。Coordinatorは「どの画面を表示するか?」(どのフローを開始するか)に答えます。Routerはナビゲーションツールであり、Coordinatorはフローオーガナイザーです。CoordinatorはRouterを使用します。VIPERではRouterが両方の機能を果たします:どこへ行くかとどのように表示するかを決定します。MVVM-CではRouterはCoordinatorが使用する別のプロトコルです。これらのパターンは相互に排他的ではなく、補完し合います。
| 特性 | Router | Coordinator | Navigator |
|---|---|---|---|
| 焦点 | ナビゲーションの仕組み | ナビゲーションフロー | グローバルアクセス |
| レベル | UIフレームワーク(push/present) | ビジネスロジック(フロー) | インフラストラクチャ |
| 画面作成 | 準備済みVCを受け取る | ファクトリ経由でVCを作成 | VCを作成 |
| ライフサイクル | ナビゲーションごとに1つ | childCoordinators | シングルトン |
Router vs Navigator — NavigatorはiOSコミュニティにおけるRouterの古い名称です。初期の実装では、Navigatorはナビゲーションメソッドを持つシングルトンクラスでした。NavigatorはグローバルRouterシングルトンです。Navigatorはよりシンプルですが、隠れた依存関係を生み出します。プロトコルベースのRouterはよりテスト可能でモジュール化されています。Navigatorは厳格なアーキテクチャがないプロジェクトに適しており、RouterはClean ArchitectureとVIPERに適しています。
よくある質問
RouterはUINavigationController(push/present/pop)の抽象化です。CoordinatorはRouterの上層レイヤーで、フロー(次にどの画面を表示するか)を管理します。Routerは仕組みを担当し、Coordinatorはナビゲーションロジックを担当します。Coordinatorは遷移を実行するためにRouterを使用します。VIPERではRouterが両方の機能を兼ね備えていますが、MVVM-Cでは分離されています。
いいえ。3〜5画面のアプリでは、navigationController.pushViewControllerの直接呼び出しで十分です。Routerは10画面以上、deep links、プッシュ通知、モジュラーアーキテクチャの場合に有用になります。Routerはナビゲーションのテストを簡素化し、任意のソース(ボタン、deep link、プッシュ通知)からの遷移を集中管理できます。
URL RouterはRouterの拡張で、deep linksとuniversal linksを解析します。URL RouterはURLとアプリ画面の間のマッピングを決定します。例:ルートmyapp://profile/42 → ProfileViewController(userId: 42)。URL Routerはプッシュ通知、QRコード、メールリンク、ユニバーサルリンクなど異なるソースからのdeep link処理を集中化します。
はい。Routerは独立して機能できます — グローバルNavigatorとしてもVIPERモジュールコンポーネントとしても。CoordinatorなしのRouterは、単にUINavigationControllerをプロトコルの背後にカプセル化するだけです。CoordinatorなしのRouterはより複雑ですが(ViewControllerが直接push/presentを呼び出す)、可能です。ベストプラクティスはRouter + Coordinatorを一緒に使用することです。
機能的には — はい、両方ともナビゲーションを集中化します。実装は異なります:iOS Router — UINavigationControllerをラップするプロトコル+クラス;Android — XMLグラフとNavControllerを備えたJetpack Navigation Component。Android Routerはフレームワーク(Navigation Component)に組み込まれており、iOS Routerはサードパーティの実装です。Android RouterはSafe Argsをサポートしていますが、iOS Routerはサポートしていません(genericsまたはCodableが必要)。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。