Kotlin DSL を使用してグラフを作成する場合、デスティネーションとナビゲーション イベントを 1 つのファイル内に維持するのは難しいことがあります。これは、独立した特徴が複数ある場合に特に当てはまります。
リンク先の抽出
デスティネーションを NavGraphBuilder
拡張関数に移動する必要があります。表示場所を定義するルートと、表示する画面の近くに表示する必要があります。たとえば、連絡先のリストを表示するデスティネーションを作成するアプリレベルのコードについて考えてみましょう。
// MyApp.kt
@Serializable
object Contacts
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
}
ナビゲーション固有のコードを別のファイルに移動する必要があります。
// ContactsNavigation.kt
@Serializable
object Contacts
fun NavGraphBuilder.contactsDestination() {
composable<Contacts> { ContactsScreen( /* ... */ ) }
}
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination()
}
}
ルートと宛先の定義がメインアプリから分離され、個別に更新できるようになりました。メインアプリは 1 つの拡張関数にのみ依存します。この場合は NavGraphBuilder.contactsDestination()
です。
NavGraphBuilder
拡張関数は、ステートレスの画面レベルのコンポーズ可能な関数と Navigation 固有のロジック間のブリッジを形成します。このレイヤでは、状態の取得元とイベントの処理方法も定義できます。
例
次のスニペットは、連絡先の詳細を表示する新しいデスティネーションを導入し、既存の連絡先リストのデスティネーションを更新して、連絡先の詳細を表示するナビゲーション イベントを公開します。
以下は、他のモジュールがアクセスできないように、独自のモジュールに対して internal
できる、一般的な画面セットです。
// ContactScreens.kt
// Displays a list of contacts
@Composable
internal fun ContactsScreen(
uiState: ContactsUiState,
onNavigateToContactDetails: (contactId: String) -> Unit
) { ... }
// Displays the details for an individual contact
@Composable
internal fun ContactDetailsScreen(contact: ContactDetails) { ... }
デスティネーションを作成する
次の NavGraphBuilder
拡張関数は、ConversationScreen
コンポーザブルを表示するデスティネーションを作成します。さらに、画面 UI の状態を提供し、画面関連のビジネス ロジックを処理する ViewModel
に画面を接続できるようになりました。
連絡先情報のデスティネーションへの移動などのナビゲーション イベントは、ViewModel
で処理されず、呼び出し元に公開されます。
// ContactsNavigation.kt
@Serializable
object Contacts
// Adds contacts destination to `this` NavGraphBuilder
fun NavGraphBuilder.contactsDestination(
// Navigation events are exposed to the caller to be handled at a higher level
onNavigateToContactDetails: (contactId: String) -> Unit
) {
composable<Contacts> {
// The ViewModel as a screen level state holder produces the screen
// UI state and handles business logic for the ConversationScreen
val viewModel: ContactsViewModel = hiltViewModel()
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
ContactsScreen(
uiState,
onNavigateToContactDetails
)
}
}
同じ方法で、ContactDetailsScreen
を表示するデスティネーションを作成できます。この場合、ビューモデルから UI 状態を取得する代わりに、NavBackStackEntry
から直接取得できます。
// ContactsNavigation.kt
@Serializable
internal data class ContactDetails(val id: String)
fun NavGraphBuilder.contactDetailsScreen() {
composable<ContactDetails> { navBackStackEntry ->
ContactDetailsScreen(contact = navBackStackEntry.toRoute())
}
}
ナビゲーション イベントをカプセル化する
デスティネーションをカプセル化する方法と同じ方法で、ナビゲーション イベントをカプセル化することで、ルートタイプが不必要に公開されるのを防ぐことができます。そのためには、NavController
に拡張関数を作成します。
// ContactsNavigation.kt
fun NavController.navigateToContactDetails(id: String) {
navigate(route = ContactDetails(id = id))
}
まとめ
連絡先を表示するためのナビゲーション コードが、アプリのナビゲーション グラフから明確に分離されました。アプリは次のことを行う必要があります。
NavGraphBuilder
拡張関数を呼び出してデスティネーションを作成する- ナビゲーション イベントに対して
NavController
拡張関数を呼び出して、これらのデスティネーションを接続する
// MyApp.kt
@Composable
fun MyApp() {
...
NavHost(navController, startDestination = Contacts) {
contactsDestination(onNavigateToContactDetails = { contactId ->
navController.navigateToContactDetails(id = contactId)
})
contactDetailsDestination()
}
}
まとめ
- 関連する画面セットのナビゲーション コードを別のファイルに配置してカプセル化する
NavGraphBuilder
に拡張関数を作成してデスティネーションを公開するNavController
で拡張関数を作成してナビゲーション イベントを公開するinternal
を使用して画面とルートタイプを非公開にする