При использовании Kotlin DSL для построения графика сохранение пунктов назначения и событий навигации в одном файле может быть затруднено. Это особенно актуально, если у вас есть несколько независимых функций.
Извлечь пункты назначения
Вам следует переместить пункты назначения в функции расширения 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()
}
}
Определения маршрутов и пунктов назначения теперь отделены от основного приложения, и вы можете обновлять их независимо. Основное приложение зависит только от одной функции расширения. В данном случае это NavGraphBuilder.contactsDestination()
.
Функция расширения NavGraphBuilder
образует мост между компонуемой функцией на уровне экрана без сохранения состояния и логикой, специфичной для навигации. Этот уровень также может определять, откуда берется состояние и как вы обрабатываете события.
Пример
В следующем фрагменте представлен новый пункт назначения для отображения сведений о контакте и обновляется существующий пункт назначения списка контактов, чтобы предоставить событие навигации для отображения сведений о контакте.
Вот типичный набор экранов, которые могут быть 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
создает пункт назначения, в котором отображается компонуемый ContactsScreen
. Кроме того, теперь он соединяет экран с 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
. В этом случае вместо получения состояния пользовательского интерфейса из модели представления вы можете получить его непосредственно из 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
, чтобы сохранить конфиденциальность экранов и типов маршрутов.