モジュール間のナビゲーション
密結合を避けて機能同士を接続します。
「モジュール間のナビゲーション」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。
ナビゲーションの課題
機能を別々のモジュールに分けると、新たな疑問が生まれます。:feature:home は、依存関係を持たずに :feature:profile の画面をどのように開けばよいのでしょうか。機能同士が直接importすると、結合が生まれ、循環依存の危険もあります。
このレッスンでは、機能を独立させたまま、ナビゲーションを通じて接続する方法を学びます。
NavHostを配置する場所
単一のNavHostは:app moduleに配置します。ここだけがすべての機能を把握してよい場所です。各機能が自分のdestinationを提供し、:appがそれらを1つのグラフに組み立てます。
// app/AppNavHost.kt
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
NavHost(navController, startDestination = HomeRoute) {
homeScreen(onProfileClick = { navController.navigate(ProfileRoute) })
profileScreen(onBack = { navController.popBackStack() })
}
}型安全なルート
最新のNavigation Composeは型安全なルートをサポートしています。ルートはマジック文字列ではなく、@Serializableオブジェクトまたはデータクラスです。各機能は自身のモジュール内でルート型を定義し、ナビゲーション契約を所有します。
// feature/profile/ProfileRoute.kt
import kotlinx.serialization.Serializable
@Serializable
data class ProfileRoute(val userId: String)機能からNavGraphBuilder拡張を公開する
重要なポイントは、各機能がNavGraphBuilder extension functionを公開して、自身のdestinationを登録することです。画面は機能側が所有し、:appはこの関数を呼び出すだけです。機能が他の機能を参照することはありません。
// feature/profile/ProfileNavigation.kt
fun NavGraphBuilder.profileScreen(onBack: () -> Unit) {
composable<ProfileRoute> { backStackEntry ->
val route: ProfileRoute = backStackEntry.toRoute()
ProfileScreen(userId = route.userId, onBack = onBack)
}
}ナビゲーションコールバックで疎結合にする
機能からnavigate(SomeOtherFeatureRoute)を直接呼び出してはいけません。そうすると、他の機能への依存が必要になるためです。代わりに、onProfileClickのようなlambda callbackを公開します。実際にどこへ遷移するかは :app module が決定します。
// feature/home/HomeNavigation.kt
fun NavGraphBuilder.homeScreen(onProfileClick: (String) -> Unit) {
composable<HomeRoute> {
HomeScreen(onUserClick = { userId -> onProfileClick(userId) })
}
}
// :home does NOT know ProfileRoute exists.:app Moduleですべてを接続する
すべてのルートを把握し、コールバックを実際のナビゲーションに接続するのは :app module だけです。ここは結合が許される唯一の場所です。役割が組み立てだからです。
// app/AppNavHost.kt
NavHost(navController, startDestination = HomeRoute) {
homeScreen(
onProfileClick = { userId ->
navController.navigate(ProfileRoute(userId)) // app knows both
}
)
profileScreen(onBack = { navController.popBackStack() })
}安全に引数を渡す
ルートが@Serializableデータクラスであるため、引数はコンパイル時に型チェックされます。destination内でtoRoute()を使って読み戻せます。文字列の解析や、実行時にarguments?.getString(...)がクラッシュする問題はもう必要ありません。
// Reading arguments inside the destination
composable<ProfileRoute> { entry ->
val args: ProfileRoute = entry.toRoute()
ProfileScreen(userId = args.userId)
}
// Or directly in a ViewModel via SavedStateHandle
val route: ProfileRoute = savedStateHandle.toRoute()api Moduleでルート型を共有する
ある機能が別の機能へ実際に遷移する必要があり、ルート型を使いたい場合があります。機能全体に依存する代わりに、@Serializableルートだけを含む小さな:feature:profile:apiモジュールでルートを公開します。重い:implモジュールは非公開のままにします。
// feature/profile/api -> only the route type
@Serializable
data class ProfileRoute(val userId: String)
// feature/home/build.gradle.kts
// home may depend on the lightweight api to build the route,
// but never on :feature:profile:impl
implementation(project(":feature:profile:api"))機能ごとのネストされたナビゲーション
複数の画面を持つ機能は、navigation<T>を使ってネストされたグラフ全体を公開できます。機能が内部のフローを所有し、:app は1つの入口でそのグラフを組み込むだけです。
// feature/onboarding/OnboardingNavigation.kt
fun NavGraphBuilder.onboardingGraph(onFinished: () -> Unit) {
navigation<OnboardingGraph>(startDestination = WelcomeRoute) {
composable<WelcomeRoute> { WelcomeScreen() }
composable<PermissionsRoute> { PermissionsScreen(onDone = onFinished) }
}
}モジュール間のディープリンク
型安全なルートはディープリンクもサポートします。機能が自身のdestination用のディープリンクURIパターンを宣言し、:app NavHost が受け取ったリンクを適切な機能の画面へ解決します。機能間のimportは必要ありません。
composable<ProfileRoute>(
deepLinks = listOf(
navDeepLink<ProfileRoute>(basePath = "https://myapp.com/profile")
)
) { entry ->
ProfileScreen(userId = entry.toRoute<ProfileRoute>().userId)
}疎結合パターンのまとめ
ここまでのルールをまとめると、次のようになります。
- 各機能がルート型とNavGraphBuilder extensionを所有します。
- 機能同士は直接ナビゲーションするのではなく、lambda callbackで意図を伝えます。
- すべての機能を把握し、コールバックを実際のルートに接続するのは:appだけです。
- ルートを共有する必要がある場合は、小さな:apiモジュールを通じて公開します。
これにより、機能を独立させ、ビルドキャッシュを利用でき、循環依存も避けられます。
理解度チェック
マルチモジュールアプリで、:feature:homeから:feature:profileの画面へユーザーを遷移させる必要があります。機能同士の結合を避ける最も適切な方法は何でしょうか。
まとめ: モジュール間のナビゲーション
機能同士を結合せずに接続する方法を学びました。
- 単一の
NavHostは、薄い:appモジュールに配置します。 - 各機能が
@SerializableルートとNavGraphBuilder拡張を所有します。 - 機能は他の機能へ直接遷移する代わりに、コールバックを公開します。
- 本当に必要な場合のみ、小さな:apiモジュールを通じてルートを共有します。ディープリンクやネストされたグラフも同じパターンに適合します。
これでマルチモジュールアプリのアーキテクチャは完了です。スケーラブルなAndroidコードベースを分割し、接続し、ナビゲーションできるようになりました。
よくある質問
「モジュール間のナビゲーション」レッスンは無料ですか?
はい。「モジュール間のナビゲーション」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。
「モジュール間のナビゲーション」で何を学びますか?
密結合を避けて機能同士を接続します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Android Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「モジュール間のナビゲーション」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAndroid Academyレッスンでコードを書いて実行できますか?
はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モジュール化する理由
- FeatureモジュールとCoreモジュール
- モジュール依存関係の管理
- モジュール間のナビゲーション