Przekazywanie danych między miejscami docelowymi

Nawigacja umożliwia dołączenie danych do operacji nawigacji przez zdefiniowanie argumentów miejsca docelowego. Na przykład miejsce docelowe profilu użytkownika może na podstawie argumentu identyfikatora użytkownika określać, który użytkownik ma zostać wyświetlony.

Ogólnie zalecamy przesyłanie między miejscami docelowymi jak najmniejszej ilości danych. Na przykład należy przekazać klucz, aby pobrać obiekt, a nie sam obiekt, ponieważ w Androidzie łączna ilość miejsca dla wszystkich zapisanych stanów jest ograniczona. Jeśli musisz przekazać duże ilości danych, użyj ViewModel zgodnie z opisem w opisie ViewModel.

Zdefiniuj argumenty docelowe

Aby przekazać dane między miejscami docelowymi, najpierw zdefiniuj argument, dodając go do miejsca docelowego, które je odbiera. W tym celu wykonaj te czynności:

  1. W Edytorze nawigacji kliknij miejsce docelowe, które odbiera argument.
  2. W panelu Atrybuty kliknij Dodaj (+).
  3. W wyświetlonym oknie Dodaj link do argumentu wpisz nazwę argumentu, typ argumentu, informację, czy argument ma wartość null, oraz w razie potrzeby wartość domyślną.
  4. Kliknij Dodaj. Zwróć uwagę, że argument pojawi się na liście Argumenty w panelu Atrybuty.
  5. Następnie kliknij odpowiednie działanie, aby przejść do tego miejsca docelowego. W panelu Atrybuty powinien być widoczny nowo dodany argument w sekcji Wartości domyślne argumentów.
  6. Widać też, że argument został dodany w formacie XML. Kliknij kartę Tekst, aby przełączyć się na widok XML. Zwróć uwagę, że Twój argument został dodany do miejsca docelowego, które go otrzymuje. Oto przykład:

     <fragment android:id="@+id/myFragment" >
         <argument
             android:name="myArg"
             app:argType="integer"
             android:defaultValue="0" />
     </fragment>
    

Obsługiwane typy argumentów

Biblioteka nawigacji obsługuje te typy argumentów:

Typ Składnia app:argType Obsługa wartości domyślnych Obsługiwane wg tras Z wartością null
Liczba całkowita app:argType="integer" Tak Tak Nie
Pływające app:argType="float" Tak Tak Nie
Długi app:argType="long" Tak – wartości domyślne zawsze muszą kończyć się sufiksem „L” (np. „123L”). Tak Nie
Wartość logiczna app:argType="boolean" Tak – „true” lub „false” Tak Nie
Ciąg znaków app:argType="ciąg znaków" Tak Tak Tak
Odniesienie do zasobu app:argType="reference" Tak – wartości domyślne muszą mieć format „@resourceType/resourceName” (np. „@style/myCustomStyle”) lub „0” Tak Nie
Niestandardowy format Parcelable app:argType="<type>", gdzie <type> to w pełni kwalifikowana nazwa klasy klasy Parcelable Obsługuje wartość domyślną „@null”. Nie obsługuje innych wartości domyślnych. Nie Tak
Niestandardowa możliwa do serializacji app:argType="<type>", gdzie <type> to w pełni kwalifikowana nazwa klasy klasy Serializable Obsługuje wartość domyślną „@null”. Nie obsługuje innych wartości domyślnych. Nie Tak
Niestandardowe wyliczenie app:argType="<type>", gdzie <type> to pełna i jednoznaczna nazwa wyliczenia Tak – wartości domyślne muszą pasować do nazwy niekwalifikującej się (np. „SUCCESS” w celu dopasowania do MyEnum.SUCCESS). Nie Nie

Jeśli typ argumentu obsługuje wartości null, możesz zadeklarować domyślną wartość null za pomocą funkcji android:defaultValue="@null".

Trasy, precyzyjne linki i identyfikatory URI wraz z ich argumentami można analizować z ciągów tekstowych. Nie można tego zrobić, używając niestandardowych typów danych, takich jak dane Parcelable i Serializable, jak pokazano w poprzedniej tabeli. Aby przekazywać niestandardowe złożone dane, przechowuj je w innym miejscu, np. w modelu ViewModel lub bazie danych, i przekazuj identyfikator tylko podczas nawigacji. Dane możesz pobierać w nowej lokalizacji po zakończeniu nawigacji.

Gdy wybierzesz jeden z typów niestandardowych, pojawi się okno Wybierz klasę z prośbą o wybranie odpowiedniej klasy dla tego typu. Na karcie Projekt możesz wybrać klasę z bieżącego projektu.

Jeśli chcesz, by biblioteka nawigacji określiła typ na podstawie podanej wartości, możesz wybrać <inferred type> (wywnioskowany typ).

Możesz zaznaczyć opcję Tablica, aby wskazać, że argument powinien być tablicą wybranej wartości Typ. Uwaga:

  • Tablice wyliczeniowe i tablice odwołań do zasobów nie są obsługiwane.
  • Tablice obsługują wartości null niezależnie od obsługi wartości null w podstawowym typie. Na przykład użycie właściwości app:argType="integer[]" pozwala użyć app:nullable="true", aby wskazać, że dopuszczalne jest przekazywanie tablicy o wartości null.
  • Tablice obsługują jedną wartość domyślną – „@null”. Tablice nie obsługują innych wartości domyślnych.

Zastępowanie argumentu docelowego w działaniu

Argumenty na poziomie miejsca docelowego i wartości domyślne są używane we wszystkich działaniach, które prowadzą do tego miejsca docelowego. W razie potrzeby możesz zastąpić domyślną wartość argumentu (lub ustawić ją, jeśli jeszcze nie istnieje), definiując argument na poziomie działania. Ten argument musi mieć taką samą nazwę i typ jak argument zadeklarowany w miejscu docelowym.

Ten kod XML deklaruje działanie za pomocą argumentu, który zastępuje argument na poziomie miejsca docelowego z poprzedniego przykładu:

<action android:id="@+id/startMyFragment"
    app:destination="@+id/myFragment">
    <argument
        android:name="myArg"
        app:argType="integer"
        android:defaultValue="1" />
</action>

Używaj bezpiecznych argumentów do przekazywania danych z zabezpieczeniami typu

Komponent Nawigacja ma wtyczkę do Gradle o nazwie Safe Args, która generuje proste klasy obiektów i konstruktora, aby zapewnić bezpieczną nawigację i dostęp do wszystkich powiązanych argumentów. Do poruszania się po danych i przekazywania danych zdecydowanie zalecamy korzystanie z bezpiecznych argumentów, ponieważ zapewnia to bezpieczeństwo typów.

Jeśli nie używasz Gradle, nie możesz użyć wtyczki Safe Args. W takich przypadkach możesz używać pakietów do bezpośredniego przekazywania danych.

Aby dodać bezpieczne argumenty do projektu, umieść w pliku build.gradle najwyższego poziomu ten parametr classpath:

Odlotowy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.7.7"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.7.7"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

Musisz też zastosować jedną z dwóch dostępnych wtyczek.

Aby wygenerować kod języka Java odpowiedniego dla Javy lub mieszanych modułów Java i Kotlin, dodaj ten wiersz do pliku build.gradle aplikacji lub modułu:

Odlotowy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Aby wygenerować kod Kotlin odpowiedni dla modułów tylko w Kotlin, dodaj:

Odlotowy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

Musisz mieć android.useAndroidX=true w pliku gradle.properties zgodnie z instrukcjami migracji do AndroidaX.

Po włączeniu bezpiecznych argumentów wygenerowany kod będzie zawierał następujące bezpieczne klasy i metody dla każdego działania oraz dla każdego miejsca docelowego wysyłania i odbierania.

  • Klasa jest tworzona dla każdego miejsca docelowego, w którym zainicjowano działanie. Nazwa tej klasy to nazwa początkowego miejsca docelowego z dopiskiem „Trasa”. Jeśli na przykład miejscem docelowym jest fragment o nazwie SpecifyAmountFragment, wygenerowana klasa nazywa się SpecifyAmountFragmentDirections.

    Ta klasa ma metodę dla każdego działania zdefiniowanego w źródłowym miejscu docelowym.

  • Dla każdego działania używanego do przekazywania argumentu tworzona jest klasa wewnętrzna, której nazwa bazuje na działaniu. Jeśli na przykład działanie to confirmationAction,, klasa to ConfirmationAction. Jeśli działanie zawiera argumenty bez wartości defaultValue, do określania ich wartości używaj powiązanej klasy działania.

  • W miejscu docelowym odbierania tworzona jest klasa. Nazwa tej klasy to nazwa miejsca docelowego ze słowem „Args”. Jeśli np. fragment docelowy to ConfirmationFragment,, wygenerowana klasa nazywa się ConfirmationFragmentArgs. Do pobierania argumentów użyj metody fromBundle() tej klasy.

Z przykładu poniżej dowiesz się, jak za pomocą tych metod ustawić argument i przekazać go do metody navigate():

Kotlin

override fun onClick(v: View) {
   val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
   val amount = amountTv.text.toString().toInt()
   val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
   v.findNavController().navigate(action)
}

Java

@Override
public void onClick(View view) {
   EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount);
   int amount = Integer.parseInt(amountTv.getText().toString());
   ConfirmationAction action =
           SpecifyAmountFragmentDirections.confirmationAction();
   action.setAmount(amount);
   Navigation.findNavController(view).navigate(action);
}

W kodzie miejsca docelowego odbioru użyj metody getArguments(), aby pobrać pakiet i użyć jego zawartości. Gdy używasz zależności -ktx, użytkownicy Kotlin mogą też używać delegata właściwości by navArgs() do uzyskiwania dostępu do argumentów.

Kotlin

val args: ConfirmationFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val tv: TextView = view.findViewById(R.id.textViewAmount)
    val amount = args.amount
    tv.text = amount.toString()
}

Java

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    TextView tv = view.findViewById(R.id.textViewAmount);
    int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount();
    tv.setText(amount + "");
}

Używanie bezpiecznych argumentów w działaniu globalnym

Gdy używasz bezpiecznych argumentów z działaniem globalnym, musisz podać wartość android:id dla głównego elementu <navigation>, jak pokazano w tym przykładzie:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/main_nav"
            app:startDestination="@id/mainFragment">

    ...

</navigation>

Nawigacja generuje klasę Directions dla elementu <navigation> na podstawie wartości android:id. Jeśli np. masz element <navigation> z elementem android:id=@+id/main_nav, wygenerowana klasa nazywa się MainNavDirections. Wszystkie miejsca docelowe w elemencie <navigation> wygenerowały metody dostępu do wszystkich powiązanych działań globalnych przy użyciu tych samych metod, które opisaliśmy w poprzedniej sekcji.

Przekazywanie danych między miejscami docelowymi za pomocą obiektów pakietu

Jeśli nie używasz Gradle, nadal możesz przekazywać argumenty między miejscami docelowymi za pomocą obiektów Bundle. Utwórz obiekt Bundle i przekaż go do miejsca docelowego za pomocą navigate(), jak w tym przykładzie:

Kotlin

val bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)

Java

Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);

W kodzie miejsca docelowego odbioru użyj metody getArguments(), aby pobrać Bundle i wykorzystać jego zawartość:

Kotlin

val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments?.getString("amount")

Java

TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));

Przekaż dane do miejsca docelowego

Możesz przekazywać dane do początkowego miejsca docelowego aplikacji. Najpierw musisz wyraźnie utworzyć element Bundle zawierający dane. Następnie użyj jednej z tych metod, aby przekazać Bundle do miejsca docelowego początkowego:

Aby pobrać dane w miejscu docelowym początkowym, wywołaj metodę Fragment.getArguments().

Uwagi dotyczące ProGuard

Jeśli zmniejszasz kod, możesz zapobiec zaciemnieniu nazw klas Parcelable, Serializable i Enum w ramach procesu minifikacji. Możesz to zrobić na 2 sposoby:

  • Używaj adnotacji @Keep.
  • Używaj reguł Keepnames.

Poniżej opisujemy te podejścia.

Używanie adnotacji @Keep

W tym przykładzie dodano adnotacje @Keep do definicji klas modelu:

Kotlin

@Keep class ParcelableArg : Parcelable { ... }

@Keep class SerializableArg : Serializable { ... }

@Keep enum class EnumArg { ... }

Java

@Keep public class ParcelableArg implements Parcelable { ... }

@Keep public class SerializableArg implements Serializable { ... }

@Keep public enum EnumArg { ... }

Używanie reguł Keepnames

Do pliku proguard-rules.pro możesz też dodać reguły keepnames, jak pokazano w tym przykładzie:

proguard-rules.pro

...

-keepnames class com.path.to.your.ParcelableArg
-keepnames class com.path.to.your.SerializableArg
-keepnames class com.path.to.your.EnumArg

...

Dodatkowe materiały

Więcej informacji o nawigacji znajdziesz w tych dodatkowych materiałach.

Próbki

Ćwiczenia z programowania

Filmy