Komponent Nawigacja udostępnia specyficzny dla domeny język Kotlin (DSL), który korzysta z konstruktorów udostępnianych przez Kotlina w bezpieczny sposób.
Ten interfejs API umożliwia deklaratywne tworzenie grafu w kodzie Kotlin, a nie wewnątrz zasobu XML. Jest to przydatne, jeśli chcesz
dynamicznie tworzyć nawigację w aplikacji. Aplikacja może na przykład pobrać konfigurację nawigacji z zewnętrznej usługi internetowej i zapisać ją w pamięci podręcznej, a potem wykorzystać tę konfigurację do dynamicznego tworzenia wykresu nawigacyjnego w funkcji onCreate()
Twojej aktywności.
Zależności
Aby użyć DSL Kotlin, dodaj tę zależność do pliku build.gradle
aplikacji:
Odlotowy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
Tworzenie wykresu
Zacznijmy od podstawowego przykładu, w którym wykorzystamy aplikację Słonecznik. Mamy 2 miejsca docelowe: home
i plant_detail
. Miejsce docelowe home
jest widoczne, gdy użytkownik po raz pierwszy uruchomi aplikację. To miejsce docelowe zawiera listę roślin z ogrodu użytkownika. Gdy użytkownik wybierze jedną z roślin, aplikacja przejdzie do miejsca docelowego plant_detail
.
Rysunek 1 przedstawia te miejsca docelowe wraz z argumentami wymaganymi przez miejsce docelowe plant_detail
i działanie to_plant_detail
, których aplikacja używa do nawigacji z home
do plant_detail
.
Hostowanie grafu Nav DSL Kotlin
Aby utworzyć wykres nawigacyjny swojej aplikacji, musisz mieć miejsce na jego umieszczenie. W tym przykładzie użyto fragmentów, dlatego wykres znajduje się w elemencie NavHostFragment
wewnątrz elementu 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>
Zwróć uwagę, że w tym przykładzie nie ustawiono atrybutu app:navGraph
. Wykres nie jest zdefiniowany jako zasób w folderze res/navigation
, więc trzeba go ustawić w procesie onCreate()
w działaniu.
W języku XML działanie wiąże identyfikator miejsca docelowego z co najmniej 1 argumentem. Jednak w przypadku korzystania z DSL w Nawigacji trasa może zawierać jej argumenty. Oznacza to, że w przypadku DSL nie trzeba wykonywać żadnych działań.
Następnym krokiem jest zdefiniowanie kilku stałych, których będziesz używać do definiowania wykresu.
Utwórz stałe dla wykresu
Wykresy nawigacyjne w formacie XML są analizowane w ramach procesu tworzenia Androida. Dla każdego atrybutu id
zdefiniowanego na wykresie tworzona jest stała liczbowa. Identyfikatory statyczne generowane podczas kompilacji nie są dostępne podczas tworzenia wykresu nawigacyjnego w czasie działania, więc DSL nawigacji używa ciągów trasy zamiast identyfikatorów. Każda trasa jest reprezentowana przez unikalny ciąg znaków. Warto zdefiniować je jako stałe, aby zmniejszyć ryzyko błędów związanych z literówką.
W przypadku obsługi argumentów są one wbudowane w ciąg trasy. Wbudowanie tej logiki w trasę może ponownie zmniejszyć ryzyko pojawienia się błędów związanych z typo.
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
Tworzenie wykresu za pomocą DSL NavGraphBuilder
Po zdefiniowaniu stałych możesz utworzyć wykres nawigacyjny.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
W tym przykładzie końcowa funkcja lambda definiuje 2 miejsca docelowe fragmentów za pomocą funkcji konstruktora DSL fragment()
. Ta funkcja wymaga ciągu trasy dla miejsca docelowego, który jest uzyskiwany ze stałych. Ta funkcja akceptuje też opcjonalną funkcję lambda na potrzeby dodatkowej konfiguracji, np. etykiety docelowej, a także osadzone funkcje kreatora dotyczące argumentów i precyzyjnych linków.
Klasa Fragment
, która zarządza interfejsem użytkownika każdego miejsca docelowego, jest przekazywana jako typ z parametrami w nawiasach kątowych (<>
). Działa to tak samo jak ustawienie atrybutu android:name
w miejscach docelowych fragmentów zdefiniowanych za pomocą kodu XML.
Nawigacja z użyciem wykresu DSL Kotlin
Możesz też przejść z home
do plant_detail
za pomocą standardowych wywołań NavController.navigation():
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
W funkcji PlantDetailFragment
możesz uzyskać wartość argumentu w następujący sposób:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
Szczegółowe informacje o tym, jak podać argumenty podczas nawigacji, znajdziesz w sekcji podawanie argumentów miejsca docelowego.
W pozostałej części tego przewodnika znajdziesz opis typowych elementów wykresu nawigacji i miejsc docelowych oraz sposobów korzystania z nich przy tworzeniu wykresu.
Miejsca docelowe
DSL Kotlin zapewnia wbudowaną obsługę 3 typów miejsc docelowych: Fragment
, Activity
i NavGraph
. Każdy z nich ma własną funkcję rozszerzenia dostępną do tworzenia i konfigurowania miejsc docelowych.
Miejsca docelowe fragmentów
Funkcję DSL fragment()
można parametryzować w klasie implementującej fragment. Na jej podstawie znajduje się unikalny ciąg trasy do przypisania do miejsca docelowego. Następnie parametr lambda pozwala podać dodatkową konfigurację zgodnie z opisem w sekcji Nawigacja za pomocą wykresu DSL Kotlin.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Miejsce docelowe aktywności
Funkcja DSL activity()
wykorzystuje unikalny ciąg trasy, który jest przypisany do tego miejsca docelowego, ale nie jest określana jako parametry na żadnej implementującej klasie aktywności. Zamiast tego ustawiasz opcjonalny element activityClass
w końcowej funkcji lambda. Ta elastyczność pozwala zdefiniować miejsce docelowe działania, które powinno być uruchamiane za pomocą intencji niejawnej, ponieważ klasa bezpośredniego działania nie miałaby sensu. Podobnie jak w przypadku miejsc docelowych fragmentów, możesz też skonfigurować etykietę, argumenty i precyzyjne linki.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
Miejsce docelowe wykresu nawigacyjnego
Funkcji DSL navigation()
możesz użyć do utworzenia zagnieżdżonego wykresu nawigacyjnego.
Ta funkcja przyjmuje 3 argumenty: trasę do przypisania do wykresu, trasę początkowego punktu docelowego wykresu i lambda do dalszej konfiguracji wykresu. Prawidłowe elementy to m.in. inne miejsca docelowe, argumenty, precyzyjne linki i opisowa etykieta miejsca docelowego.
Ta etykieta może być przydatna do powiązania wykresu nawigacyjnego z komponentami interfejsu za pomocą Nawigacja
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
Obsługa niestandardowych miejsc docelowych
Jeśli używasz nowego typu miejsca docelowego, który nie obsługuje bezpośrednio DSL Kotlin, możesz dodać te miejsca docelowe do DSL Kotlin za pomocą addDestination()
:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
Możesz też użyć jednoargumentowego operatora plusa, aby dodać nowo utworzone miejsce docelowe bezpośrednio do wykresu:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
Podaję argumenty docelowe
Dowolne miejsce docelowe może definiować argumenty opcjonalne lub wymagane. Działania można definiować za pomocą funkcji argument()
w NavDestinationBuilder
, która jest klasą bazową wszystkich typów konstruktora miejsc docelowych. Ta funkcja przyjmuje nazwę argumentu jako ciąg znaków i lambda, która służy do stworzenia i konfiguracji NavArgument
.
Funkcja lambda pozwala określić typ danych argumentu, w razie potrzeby wartość domyślną oraz określić, czy ma ona wartość null.
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
Jeśli podano defaultValue
, można wywnioskować typ. Jeśli podana jest zarówno wartość defaultValue
, jak i type
, typy muszą być takie same. Pełną listę dostępnych typów argumentów znajdziesz w dokumentacji NavType.
Dostarczanie typów niestandardowych
Niektóre typy, np. ParcelableType
i SerializableType
, nie obsługują analizy wartości ciągów używanych przez trasy lub precyzyjne linki.
Dzieje się tak, ponieważ nie polegają na odczuciu w czasie działania. Udostępniając niestandardową klasę NavType
, możesz dokładnie kontrolować sposób analizowania typu na podstawie trasy lub precyzyjnego linku. Dzięki temu możesz użyć serializacji Kotlin lub innych bibliotek, aby zapewnić bezrefleksyjne kodowanie i dekodowanie niestandardowego typu.
Na przykład klasa danych reprezentująca parametry wyszukiwania przekazywane do ekranu wyszukiwania może implementować zarówno Serializable
(aby zapewnić obsługę kodowania/dekodowania), jak i Parcelize
(do obsługi zapisywania w Bundle
i przywracania z niego):
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
Niestandardowy element NavType
można zapisać w następujący sposób:
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 parseValue(value: String): SearchParameters {
return Json.decodeFromString<SearchParameters>(value)
}
// Only required when using Navigation 2.4.0-alpha07 and lower
override val name = "SearchParameters"
}
Można go używać w strumieniu DSL Kotlin jak każdego innego typu:
fragment<SearchFragment>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
W tym przykładzie użyto serializacji Kotlin do analizy wartości ciągu znaków, co oznacza, że przy przejściu do miejsca docelowego należy również użyć serializacji Kotlin, aby mieć pewność, że formaty są zgodne:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = Uri.encode(Json.encodeToString(params))
navController.navigate("${nav_routes.plant_search}/$searchArgument")
Parametr można uzyskać z argumentów w miejscu docelowym:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
Precyzyjne linki
Precyzyjne linki można dodawać do dowolnych miejsc docelowych, tak jak w przypadku wykresów nawigacyjnych opartych na języku XML. Wszystkie procedury opisane w sekcji Tworzenie precyzyjnego linku do miejsca docelowego mają zastosowanie do procesu tworzenia precyzyjnego precyzyjnego linku za pomocą DSL Kotlin.
Przy tworzeniu ukrytego precyzyjnego linku nie masz jednak zasobu nawigacji XML, który można by przeanalizować pod kątem elementów <deepLink>
. Dlatego nie możesz umieszczać elementu <nav-graph>
w pliku AndroidManifest.xml
. Zamiast tego musisz ręcznie dodać do aktywności filtry intencji.
Dostarczony filtr intencji powinien pasować do podstawowego wzorca adresu URL, działania i typu MIME precyzyjnych linków do Twojej aplikacji.
Za pomocą funkcji DSL deepLink()
możesz podać bardziej szczegółową wartość deeplink
dla każdego miejsca docelowego z precyzyjnymi linkami. Ta funkcja akceptuje obiekt NavDeepLink
zawierający String
reprezentujący wzorzec identyfikatora URI, String
reprezentujący działania intencji i String
reprezentujący mimeType .
Na przykład:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
Możesz dodać nieograniczoną liczbę precyzyjnych linków. Za każdym razem, gdy wywołujesz deepLink()
, do listy, która jest używana w przypadku tego miejsca docelowego, jest dodawany nowy precyzyjny link.
Poniżej przedstawiamy bardziej skomplikowany scenariusz dotyczący precyzyjnych linków, który określa też ścieżkę i parametry oparte na zapytaniach:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
Aby uprościć definicję, możesz użyć interpolacji ciągów znaków.
Ograniczenia
Wtyczka Safe Args jest niezgodna z DSL Kotlin, ponieważ szuka ona plików zasobów XML, aby wygenerować klasy Directions
i Arguments
.
Więcej informacji
Aby dowiedzieć się, jak zapewnić bezpieczeństwo typów w kodzie DSL Kotlin i nawigacji w komponencie, zajrzyj na stronę Bezpieczeństwo nawigacji.