Fragmenty i DSL Kotlin

Komponent Nawigacja udostępnia język oparty na domenie Kotlin. który wykorzystuje mechanizm bezpiecznego typu pliku Kotlin budownicze , Ten interfejs API umożliwia deklaratywne skomponowanie grafu w kodzie Kotlin, a nie niż w zasobie XML. Może to być przydatne, jeśli chcesz tworzyć porusza się dynamicznie. Na przykład aplikacja może pobrać i zapisać w pamięci podręcznej konfigurację nawigacji z zewnętrznej usługi sieciowej, a następnie użyć jej aby dynamicznie tworzyć wykres nawigacji w onCreate().

Zależności

Aby używać DSL Kotlin z fragmentami, dodaj następującą zależność do funkcji Plik build.gradle:

Odlotowe

dependencies {
    def nav_version = "2.8.0"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.8.0"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Tworzenie wykresu

Oto podstawowy przykład oparty na kluczu Słonecznik Do tego celu mamy na przykład dwa miejsca docelowe: home i plant_detail. home miejsce docelowe jest obecne przy pierwszym uruchomieniu aplikacji przez użytkownika. To miejsce docelowe wyświetla listę roślin z ogrodu użytkownika. Gdy użytkownik wybierze jedną z tych opcji: rośliny, aplikacja przejdzie do miejsca docelowego plant_detail.

Rys. 1 przedstawia te miejsca docelowe wraz z argumentami wymaganymi przez miejsce docelowe plant_detail i działanie to_plant_detail używane przez aplikację aby przejść z home do plant_detail.

Aplikacja Sunflower ma 2 miejsca docelowe wraz z działaniem, które
            ich łączy.
Rysunek 1. Aplikacja Sunflower ma 2 miejsca docelowe: home i plant_detail oraz działanie, które ich łącząca.

Hosting grafu nawigacyjnego DSL Kotlin

Aby utworzyć wykres nawigacji dla aplikacji, musisz mieć miejsce do przechowywania wykres. W tym przykładzie użyto fragmentów, więc wykres jest przechowywany w NavHostFragment wewnątrz 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 atrybut app:navGraph nie jest ustawiony. Wykres nie jest zdefiniowany jako zasób w folderu res/navigation, więc należy go ustawić jako część folderu onCreate() w trakcie aktywności.

Działanie w pliku XML wiąże identyfikator miejsca docelowego z co najmniej jednym argumentem. Jednak w przypadku korzystania z nawigacji DSL trasa może zawierać argumenty trasę. Oznacza to, że przy korzystaniu z DSL nie ma koncepcji działań.

Następnym krokiem jest zdefiniowanie tras, których będziesz używać do definiowania wykres.

Tworzenie tras do wykresu

Wykresy nawigacyjne oparte na formacie XML są analizowane jako część procesu tworzenia Androida. Dla każdego elementu id tworzona jest stała liczbowa. zdefiniowany na wykresie. Te statyczne identyfikatory wygenerowane podczas kompilacji nie są dostępne przy tworzeniu wykresu nawigacji w czasie działania, dzięki czemu korzysta z funkcji serializowalności zamiast Identyfikatory. Każda trasa jest reprezentowana przez inny typ.

Gdy rozmawiamy z argumentami, są one wbudowane w proces . Dzięki temu masz pewność, że podczas pisania dla argumentów nawigacyjnych.

@Serializable data object Home
@Serializable data class Plant(val id: String)

Po zdefiniowaniu tras możesz utworzyć wykres nawigacji.

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)
    }
}

W tym przykładzie 2 miejsca docelowe fragmentów są zdefiniowane za pomocą atrybutu fragment() Funkcja konstruktora DSL. Ta funkcja wymaga dwóch typów ,

Najpierw zajęcia w usłudze Fragment , który udostępnia interfejs użytkownika tego miejsca docelowego. Ustawienie ma taki sam skutek jak ustawianie atrybutu android:name w zdefiniowanych docelowych miejscach docelowych fragmentów przy użyciu kodu XML.

Po drugie, trasa. Musi to być serializowalny typ, który rozciąga się od Any. it powinien zawierać wszelkie argumenty nawigacyjne używane przez to miejsce docelowe, i ich rodzajach.

Funkcja ta akceptuje też opcjonalną funkcję lambda na potrzeby dodatkowej konfiguracji, takiej jak jak etykieta docelowa, jak również osadzone funkcje kreatora i precyzyjnych linków.

Możesz też przejść z home do plant_detail, korzystając z NavController.navigate(). połączenia:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate(route = PlantDetail(id = plantId))
}

W narzędziu PlantDetailFragment można uzyskać argumenty nawigacyjne, uzyskując bieżącego NavBackStackEntry. i dzwoniąc toRoute , aby uzyskać wystąpienie trasy.

val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

Jeśli PlantDetailFragment używa ViewModel, uzyskaj wystąpienie trasy za pomocą SavedStateHandle.toRoute.

val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

W pozostałej części tego przewodnika znajdziesz opis często używanych elementów wykresu nawigacji, miejsc docelowych, i jak ich używać do tworzenia wykresów.

Miejsca docelowe

Kotlin DSL ma wbudowaną obsługę trzech typów miejsc docelowych: Fragment, Activity i NavGraph miejsca docelowe, z których każde ma własne wbudowana funkcja rozszerzenia dostępna do tworzenia i konfigurowania miejsce docelowe.

Miejsca docelowe fragmentów kodu

fragment() Funkcję DSL można zdefiniować za pomocą klasy fragmentu dla interfejsu użytkownika i funkcji typ trasy użyty do jednoznacznego wskazania miejsca docelowego z dodanym parametrem lambda gdzie można wprowadzić dodatkową konfigurację, zgodnie z opisem w sekcji Nawigacja z wykresem Kotlin DSL.

fragment<MyFragment, MyRoute> {
   label = getString(R.string.fragment_title)
   // custom argument types, deepLinks
}

Miejsce docelowe aktywności

activity() Funkcja DSL przyjmuje parametr typu trasy, ale nie ma parametru wszystkich implementujących klas aktywności. Zamiast tego możesz ustawić opcjonalny activityClass w lambda na końcu. Dzięki tej elastyczności możesz zdefiniować miejsce docelowe aktywności działania, które należy rozpocząć za pomocą domyślnego intencja, gdzie jawny nie miałyby sensu. Podobnie jak w przypadku miejsc docelowych fragmentów, możesz też skonfigurować etykietę, niestandardowe argumenty i precyzyjne linki.

activity<MyRoute> {
   label = getString(R.string.activity_title)
   // custom argument types, deepLinks...

   activityClass = MyActivity::class
}

navigation() Za pomocą funkcji DSL można utworzyć zagnieżdżoną nawigację wykres. Ta funkcja przyjmuje typ trasy, którą należy przypisać do tego wykresu. Przyjmuje też 2 argumenty: trasę początkowego miejsca docelowego wykresu, a parametr lambda pozwoli skonfigurować wykres. Prawidłowe elementy to między innymi inne miejsca docelowe i argument niestandardowy typów precyzyjnych linków oraz etykietę opisową miejsce docelowe. Ta etykieta może być przydatna do powiązania grafu nawigacyjnego z komponentami interfejsu za pomocą NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = 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 za pomocą lustrzanek DSL Kotlin, 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 plus, aby dodać nowe miejsce docelowe bezpośrednio na wykres:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

Podawanie argumentów miejsca docelowego

Argumenty miejsca docelowego można zdefiniować jako część klasy trasy. Mogą to być zdefiniowane w taki sam sposób jak dla każdej klasy Kotlin. Wymagane argumenty to zdefiniowane jako typy niedopuszczające wartości null, a opcjonalne argumenty są zdefiniowane domyślnie .

Bazowy mechanizm reprezentowania tras i ich argumentów to ciąg znaków oparte na danych. Używanie ciągów tekstowych do modelowania tras umożliwia przechowywanie stanu nawigacji przywrócone z dysku podczas konfiguracji zmian i procesu inicjowanego przez system śmierć. Z tego powodu każdy argument nawigacji musi być serializowalny, czyli powinien mieć , która konwertuje w pamięci reprezentację wartości argumentu na String

Serializacja Kotlin wtyczka automatycznie generuje metody serializacji dla podstawowego , gdy Do obiektu została dodana adnotacja @Serializable.

@Serializable
data class MyRoute(
  val id: String,
  val myList: List<Int>,
  val optionalArg: String? = null
)

fragment<MyFragment, MyRoute>

Podawanie typów niestandardowych

W przypadku niestandardowych typów argumentów musisz podać niestandardową klasę NavType. Ten pozwala kontrolować, w jaki sposób Twój typ jest analizowany z trasy lub precyzyjnego linku.

Na przykład trasa używana do zdefiniowania ekranu wyszukiwania może zawierać klasę, która reprezentuje parametry wyszukiwania:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

Niestandardowy obiekt NavType może być zapisany w ten 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 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)
  }
}

Możesz go następnie użyć w DSL Kotlin tak jak każdego innego:

fragment<SearchFragment, SearchRoute> {
    label = getString(R.string.plant_search_title)
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}

Podczas nawigowania do miejsca docelowego utwórz instancję trasy:

val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))

Ten parametr można pobrać z trasy w miejscu docelowym:

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters

Precyzyjne linki

Precyzyjne linki można dodawać do każdego miejsca docelowego, tak jak w przypadku witryny opartej na formacie XML. wykres nawigacyjny. Wszystkie procedury opisane w sekcji Tworzenie precyzyjnego linku dotyczące miejsca docelowego. tworzenia precyzyjnego linku za pomocą lustrzanek DSL Kotlin.

Podczas tworzenia niejawnego precyzyjnego linku nie masz jednak zasobu nawigacji XML, który można przeanalizować <deepLink> elementów. Dlatego nie można polegać na umieszczeniu <nav-graph> element w pliku AndroidManifest.xml i musi zamiast tego dodać atrybut intent ręcznie. Zamiar Podany filtr powinien pasować do ścieżki podstawowej, działania i typu MIME precyzyjne linki do aplikacji.

Precyzyjne linki są dodawane do miejsca docelowego przez wywołanie w nim funkcji deepLink lambda dla miejsca docelowego. Akceptuje on trasę jako typ z parametrami, a basePath jako ścieżki podstawowej adresu URL używanego na potrzeby precyzyjnego linku.

Możesz również dodać działanie i typ MIME za pomocą funkcji deepLinkBuilder lambda na końcu.

Ten przykład pozwala utworzyć identyfikator URI precyzyjnego linku dla miejsca docelowego 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/*"
  }
}

Format identyfikatora URI

Format identyfikatora URI precyzyjnego linku jest generowany automatycznie na podstawie pól trasy przy użyciu tych reguł:

  • Wymagane parametry są dodawane jako parametry ścieżki (przykład: /{id}).
  • Parametry z wartością domyślną (parametry opcjonalne) są dołączane jako zapytanie parametry (przykład: ?name={name})
  • Kolekcje są dodawane jako parametry zapytania (przykład: ?items={value1}&items={value2}).
  • Kolejność parametrów odpowiada kolejności pól na trasie

Na przykład ten typ trasy:

@Serializable data class PlantDetail(
  val id: String,
  val name: String,
  val colors: List<String>,
  val latinName: String? = null,
)

ma wygenerowany format URI:

basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}

Nie ma ograniczeń co do liczby precyzyjnych linków, które możesz dodać. Za każdym razem, gdy do kogoś dzwonisz deepLink() nowy precyzyjny link zostanie dołączony do listy, która jest przechowywana w tym miejscu docelowym.

Ograniczenia

Wtyczka Safe Args niekompatybilny z DSL Kotlin, ponieważ wtyczka szuka plików zasobów XML w celu wygenerować zajęcia Directions i Arguments.