Fragmenty i DSL Kotlin

Komponent Nawigacja udostępnia język oparty na domenie Kotlin. która bazuje na technologii Kotlin bezpiecznej dla typu budownicze , Ten interfejs API umożliwia deklaratywnie tworzenie grafu w kodzie Kotlina, a nie w zasobach XML. Może to być przydatne, jeśli chcesz tworzyć porusza się dynamicznie. Aplikacja może na przykład pobierać i przechowywać w pamięci podręcznej konfigurację nawigacji z zewnętrznej usługi internetowej, a następnie używać tej konfiguracji do dynamicznego tworzenia grafu nawigacji w funkcji onCreate() aktywności.

Zależności

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

Groovy

dependencies {
    def nav_version = "2.9.3"

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

Kotlin

dependencies {
    val nav_version = "2.9.3"

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

Tworzenie wykresu

Oto podstawowy przykład oparty na aplikacji Sunflower. W tym przykładzie mamy 2 miejsca docelowe: homeplant_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 oraz działanie, które je łączy.
Rysunek 1. Aplikacja Sunflower ma 2 miejsca docelowe: homeplant_detail, a także działanie, które je łączy.

Hostowanie grafu nawigacyjnego Kotlin DSL

Aby utworzyć wykres nawigacyjny aplikacji, musisz mieć miejsce do przechowywania wykres. W tym przykładzie użyto fragmentów, dlatego 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.

W kodzie XML działanie łączy identyfikator miejsca docelowego z co najmniej 1 argumentem. Jednak podczas korzystania z DSL nawigacji trasa może zawierać argumenty jako część trasy. 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 na wykresie

Grafiki nawigacji w formacie XML są analizowane w ramach procesu kompilacji Androida. Dla każdego elementu id tworzona jest stała liczbowa. zdefiniowany na wykresie. Te identyfikatory statyczne generowane w czasie kompilacji nie są dostępne podczas tworzenia grafu nawigacji w czasie wykonywania, dlatego w jego przypadku język programowania DSL do nawigacji używa typów serializowanych zamiast identyfikatorów. Każda trasa ma swój unikalny typ.

Gdy rozmawiamy z argumentami, są one wbudowane w proces . Dzięki temu masz pewność, że argumenty nawigacji są bezpieczne.

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

Po zdefiniowaniu tras możesz utworzyć graf 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 klasa Fragment, która zapewnia interfejs użytkownika dla tego miejsca docelowego. Ustawienie tej opcji ma taki sam skutek jak ustawienie atrybutu android:name w miejscach docelowych fragmentów zdefiniowanych za pomocą 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 do dodatkowej konfiguracji, np. etykiety docelowej, a także osadzone funkcje kreatora do niestandardowych argumentów i linków bezpośrednich.

Na koniec możesz przejść z home do plant_detail za pomocą NavController.navigate():

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

PlantDetailFragment możesz uzyskać argumenty nawigacji, uzyskując bieżący obiekt NavBackStackEntry i wywołując na nim metodę toRoute, aby uzyskać instancję trasy.

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

Jeśli aplikacja PlantDetailFragment używa modułu ViewModel, pobierz instancję trasy za pomocą funkcji SavedStateHandle.toRoute.

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

W dalszej części tego przewodnika opisujemy typowe elementy grafu nawigacyjnego, czyli miejsca docelowe, oraz sposób ich wykorzystania podczas tworzenia grafu.

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

Funkcja DSL activity() przyjmuje parametr typu dla trasy, ale nie jest parametryzowana pod kątem żadnej klasy implementującej aktywność. 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 kontynuować skonfigurować wykres. Dozwolone elementy to inne miejsca docelowe, niestandardowe typy argumentów, precyzyjne linki i etykieta opisowa miejsca docelowego. Ta etykieta może być przydatna do powiązania grafu nawigacji z komponentami interfejsu użytkownika 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óre nie obsługuje bezpośrednio Kotlin DSL, możesz dodać te miejsca docelowe do Kotlin DSL 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ć operatora jednoargumentowego plusa, aby dodać nowo utworzone miejsce docelowe bezpośrednio do grafu:

// 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. Można je zdefiniować w taki sam sposób jak dowolną klasę Kotlina. Wymagane argumenty są zdefiniowane jako typy, które nie mogą być puste, a opcjonalne argumenty są zdefiniowane z wartościami domyślnymi.

Mechanizm reprezentowania tras i ich argumentów opiera się na ciągach znaków. 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ć serializowany, czyli musi mieć metodę, która konwertuje reprezentację wartości argumentu w pamięci na String.

Wtyczka do serializacji w Kotlinie automatycznie generuje metody serializacji dla podstawowych typów, gdy do obiektu zostanie 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. Dzięki temu możesz dokładnie określić, jak Twój typ ma być 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
@Parcelize
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

Niestandardowa wartość NavType może mieć postać:

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 potem używać w Kotlin DSL tak jak każdego innego typu:

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

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

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

Parametr można uzyskać z trasy w miejscu docelowym:

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

Precyzyjne linki

Precyzyjne linki można dodawać do dowolnego miejsca docelowego, tak jak w przypadku XML-owego grafu nawigacyjnego. Do procesu tworzenia precyzyjnego linku za pomocą Kotlin DSL mają zastosowanie wszystkie procedury zdefiniowane w sekcji Tworzenie precyzyjnego linku do miejsca docelowego.

Podczas tworzenia domyślnego precyzyjnego linku nie masz jednak zasobu nawigacji XML, który można by przeanalizować pod kątem elementów <deepLink>. Dlatego nie można polegać na umieszczeniu <nav-graph> element w pliku AndroidManifest.xml i musi zamiast tego dodać atrybut intent ręcznie dodawać filtry do aktywności. Podany przez Ciebie filtr intencji powinien pasować do ścieżki podstawowej, działania i typu MIME precyzyjnych linków w 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, 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.

W tym przykładzie tworzymy identyfikator URI precyzyjnego linku do 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 parametry zapytania (np. ?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 taki 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 limitu 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 prowadzonej w przypadku tego miejsca docelowego.

Ograniczenia

Wtyczka Safe Args niekompatybilny z DSL Kotlin, ponieważ wtyczka szuka plików zasobów XML w celu do wygenerowania klas Directions i Arguments.