Компонент Navigation предоставляет предметно-ориентированный язык (DSL) на основе Kotlin, который использует типобезопасные построители Kotlin. Этот API позволяет декларативно создавать граф в коде Kotlin, а не в ресурсе XML. Это может быть полезно, если вы хотите создать навигацию по приложению динамически. Например, приложение может скачать и кэшировать конфигурацию навигации из внешнего веб-сервиса, а затем использовать ее для динамического построения графа навигации в функции onCreate().
Связанные запросы
Чтобы использовать Kotlin DSL с фрагментами, добавьте в файл build.gradle своего приложения следующую зависимость:
Классный
dependencies { def nav_version = "2.10.2" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Котлин
dependencies { val nav_version = "2.10.2" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Построение графика
Ниже приведен простой пример на основе приложения Sunflower. В этом примере у нас есть два пункта назначения: home и plant_detail. Пункт назначения home
отображается при первом запуске приложения. В этом пункте назначения
отображается список растений из сада пользователя. Когда пользователь выбирает одно из растений, приложение переходит к пункту назначения plant_detail.
На рисунке 1 показаны эти пункты назначения, а также аргументы, необходимые для пункта назначения plant_detail, и действие to_plant_detail, которое приложение использует для перехода от home к plant_detail.
home и plant_detail, а также действие, которое их соединяет.Размещение графа навигации Kotlin DSL
Прежде чем создавать граф навигации приложения, необходимо определить, где он будет размещаться. В этом примере используются фрагменты, поэтому граф размещается в NavHostFragment внутри FragmentContainerView:
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
Обратите внимание, что в этом примере атрибут app:navGraph не задан. Граф не определен как ресурс в папке res/navigation, поэтому его нужно задать в рамках процесса onCreate() в объекте activity.
В XML действие связывает идентификатор целевой страницы с одним или несколькими аргументами. Однако при использовании Navigation DSL маршрут может содержать аргументы как часть маршрута. Это означает, что при использовании DSL нет концепции действий.
Следующий шаг – определить маршруты, которые вы будете использовать при определении графа.
Создайте маршруты для графа
Графы навигации на основе XML анализируются в процессе сборки Android. Для каждого атрибута id, определенного в графе, создается числовая константа. Эти статические идентификаторы, генерируемые во время сборки, недоступны при построении графа навигации во время выполнения, поэтому Navigation DSL использует вместо идентификаторов сериализуемые типы. Каждый маршрут представлен уникальным типом.
Аргументы встроены в тип маршрута. Это позволяет вам иметь типобезопасность для ваших аргументов навигации.
@Serializable data object Home
@Serializable data class Plant(val id: String)
Как создать граф с помощью NavGraphBuilder DSL
Определив маршруты, вы можете создать граф навигации.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = Home
) {
fragment<HomeFragment, Home> {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment, PlantDetail> {
label = resources.getString(R.string.plant_detail_title)
}
}
В этом примере два пункта назначения фрагментов определены с помощью функции построителя DSL fragment(). Для этой функции требуется два аргумента типа.
Во-первых, класс Fragment, который предоставляет пользовательский интерфейс для этого пункта назначения. Это аналогично установке атрибута android:name для целевых фрагментов, определенных с помощью XML.
Второй – маршрут. Это должен быть сериализуемый тип, расширяющий Any. Он должен содержать все аргументы навигации, которые будут использоваться этим пунктом назначения, и их типы.
Функция также принимает необязательную лямбду для дополнительной конфигурации, например метку назначения, а также встроенные функции построителя для пользовательских аргументов и ссылок на контент.
Навигация с помощью графа Kotlin DSL
Наконец, вы можете перейти от home к plant_detail с помощью вызовов NavController.navigate():
private fun navigateToPlant(plantId: String) {
findNavController().navigate(route = PlantDetail(id = plantId))
}
В PlantDetailFragment аргументы навигации можно получить, вызвав NavBackStackEntry и toRoute, чтобы получить экземпляр маршрута.
val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
Если PlantDetailFragment использует ViewModel, получите экземпляр маршрута с помощью SavedStateHandle.toRoute.
val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id
В оставшейся части этого руководства описываются общие элементы графа навигации, пункты назначения и способы их использования при построении графа.
Направления
Kotlin DSL поддерживает три типа пунктов назначения: Fragment, Activity и NavGraph. Для каждого из них есть встроенная функция расширения, которая позволяет создавать и настраивать пункт назначения.
Целевые страницы фрагментов
Функция DSL fragment() может быть параметризована с помощью класса фрагмента для пользовательского интерфейса и типа маршрута, используемого для уникальной идентификации этого пункта назначения, за которым следует лямбда-выражение, где вы можете предоставить дополнительную конфигурацию, как описано в разделе Навигация с помощью графа Kotlin DSL.
fragment<MyFragment, MyRoute> {
label = getString(R.string.fragment_title)
// custom argument types, deepLinks
}
Место проведения мероприятия
Функция DSL activity() принимает параметр типа для маршрута, но не параметризована ни для какого реализующего класса действия. Вместо этого вы задаете необязательный параметр activityClass в завершающем лямбда-выражении. Это позволяет определить целевую активность для активности, которая должна запускаться с помощью неявного намерения, когда явный класс активности не имеет смысла. Как и в случае с пунктами назначения фрагментов, вы также можете настроить ярлык, пользовательские аргументы и ссылки на контент.
activity<MyRoute> {
label = getString(R.string.activity_title)
// custom argument types, deepLinks...
activityClass = MyActivity::class
}
Целевая страница графа навигации
Функцию DSL navigation() можно использовать для создания вложенного графа навигации. Эта функция принимает параметр типа для маршрута, который нужно назначить этому графу. Он также принимает два аргумента: маршрут начального пункта назначения графа и лямбда-выражение для дальнейшей настройки графа. Допустимые элементы включают другие целевые страницы, типы пользовательских аргументов, ссылки на контент и описательный ярлык для целевой страницы.
Этот ярлык можно использовать для привязки графа навигации к компонентам интерфейса с помощью NavigationUI.
@Serializable data object HomeGraph
@Serializable data object Home
navigation<HomeGraph>(startDestination = Home) {
// label, other destinations, deep links
}
Поддержка собственных целевых сервисов
Если вы используете новый тип пункта назначения, который не поддерживает Kotlin DSL напрямую, вы можете добавить эти пункты назначения в Kotlin DSL с помощью addDestination():
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
В качестве альтернативы вы также можете использовать унарный оператор сложения, чтобы добавить вновь созданный пункт назначения непосредственно в граф:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Как передавать аргументы в пункт назначения
Аргументы пункта назначения можно определить как часть класса маршрута. Их можно определить так же, как и для любого класса Kotlin. Обязательные аргументы определяются как типы, не допускающие значения NULL, а необязательные – со значениями по умолчанию.
Базовый механизм представления маршрутов и их аргументов основан на строках. Использование строк для моделирования маршрутов позволяет сохранять состояние навигации и восстанавливать его с диска во время изменений конфигурации и завершения процесса, инициированного системой. Поэтому каждый аргумент навигации должен быть сериализуемым, то есть у него должен быть метод, который преобразует представление значения аргумента в памяти в String.
Плагин сериализации Kotlin автоматически генерирует методы сериализации для базовых типов, когда к объекту добавляется аннотация @Serializable.
@Serializable
data class MyRoute(
val id: String,
val myList: List<Int>,
val optionalArg: String? = null
)
fragment<MyFragment, MyRoute>
Предоставление специальных типов
Для специальных типов аргументов необходимо предоставить специальный класс NavType. Это позволяет точно контролировать, как ваш тип анализируется из маршрута или ссылки на контент.
Например, маршрут, используемый для определения экрана поиска, может содержать класс, представляющий параметры поиска:
@Serializable
data class SearchRoute(val parameters: SearchParameters)
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
Специальный параметр NavType можно записать следующим образом:
val SearchParametersType = object : NavType<SearchParameters>(
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: SearchParameters) {
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): SearchParameters {
return bundle.getParcelable(key) as SearchParameters
}
override fun serializeAsValue(value: SearchParameters): String {
// Serialized values must always be Uri encoded
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): SearchParameters {
// Navigation takes care of decoding the string
// before passing it to parseValue()
return Json.decodeFromString<SearchParameters>(value)
}
}
Затем его можно использовать в Kotlin DSL, как и любой другой тип:
fragment<SearchFragment, SearchRoute>(
typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
) {
label = getString(R.string.plant_search_title)
}
При переходе к пункту назначения создайте экземпляр маршрута:
val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))
Параметр можно получить из маршрута в целевом сервисе:
val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters
Ссылки на контент
Ссылки на контент можно добавлять в любой пункт назначения, как и в случае с графом навигации на основе XML. Все процедуры, описанные в разделе Создание ссылки на контент для пункта назначения, применимы к процессу создания ссылки на контент с помощью Kotlin DSL.
Однако при создании неявной ссылки на контент у вас нет ресурса навигации XML, который можно проанализировать на наличие элементов <deepLink>. Поэтому вы не можете полагаться на размещение элемента <nav-graph> в файле AndroidManifest.xml и должны вручную добавить фильтры намерений в объект activity. Фильтр intent должен соответствовать базовому пути, действию и MIME-типу ссылок на контент в приложении.
Ссылки на контент добавляются в пункт назначения путем вызова функции deepLink внутри лямбда-выражения пункта назначения. Он принимает маршрут как параметризованный тип и параметр basePath для базового пути URL, используемого для ссылки на контент.
Вы также можете добавить действие и MIME-тип, используя завершающее лямбда-выражение deepLinkBuilder.
В примере ниже создается URI ссылки на контент для целевой страницы Home.
@Serializable data object Home
fragment<HomeFragment, Home>{
deepLink<Home>(basePath = "www.example.com/home"){
// Optionally, specify the action and/or mime type that this destination
// supports
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
}
Формат URI
Формат URI ссылки на контент автоматически генерируется из полей маршрута по следующим правилам:
- Обязательные параметры добавляются как параметры пути (пример:
/{id}). - Параметры со значением по умолчанию (необязательные параметры) добавляются как параметры запроса (пример:
?name={name}). - Коллекции добавляются в виде параметров запроса (пример:
?items={value1}&items={value2}). - Порядок параметров соответствует порядку полей в маршруте.
Например, следующий тип маршрута:
@Serializable data class PlantDetail(
val id: String,
val name: String,
val colors: List<String>,
val latinName: String? = null,
)
имеет следующий сгенерированный формат URI:
basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}
Количество ссылок на контент не ограничено. При каждом вызове deepLink() новая ссылка на контент добавляется в список, который ведется для этого целевого ресурса.
Ограничения
Плагин Safe Args несовместим с Kotlin DSL, поскольку он ищет XML-файлы ресурсов для создания классов Directions и Arguments.