Przekazywanie danych między miejscami docelowymi

Funkcja nawigacji umożliwia dołączanie danych do operacji nawigacji przez zdefiniowanie argumentów dla miejsca docelowego. Na przykład miejsce docelowe profilu użytkownika może używać argumentu identyfikatora użytkownika, aby określić, którego użytkownika wyświetlić.

Ogólnie zalecamy przekazywanie między miejscami docelowymi tylko minimalnej ilości danych. Na przykład, aby pobrać obiekt, należy przekazać klucz, a nie sam obiekt, ponieważ łączna ilość miejsca na wszystkie zapisane stany jest ograniczona na Androidzie. Jeśli chcesz przekazać duże ilości danych, użyj ViewModel zgodnie z opisem w omówieniu ViewModel.

Definiowanie argumentów miejsca docelowego

Aby przekazywać dane między punktami docelowymi, najpierw zdefiniuj argument, dodając go do punktu docelowego, który go odbiera. Aby to zrobić:

  1. W Edytorze nawigacji kliknij miejsce docelowe, które ma otrzymywać argument.
  2. W panelu Atrybuty kliknij Dodaj (+).
  3. W wyświetlonym oknie Dodaj link argumentu wpisz nazwę argumentu, jego typ, informację, czy argument może być pusty, oraz wartość domyślną (jeśli jest potrzebna).
  4. Kliknij Dodaj. Zwróć uwagę, że argument jest teraz widoczny na liście Argumenty w panelu Atrybuty.
  5. Następnie kliknij odpowiednie działanie, które przeniesie Cię do tego miejsca docelowego. W panelu Atrybuty nowo dodany argument powinien się teraz wyświetlać w sekcji Domyślne wartości argumentu.
  6. Możesz też sprawdzić, że argument został dodany w pliku XML. Kliknij kartę Tekst, aby przełączyć się na widok XML. Zobaczysz, że argument został dodany do miejsca docelowego, które go przyjmuje. Przykład:

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

Obsługiwane typy argumentów

Biblioteka Nawigacja obsługuje te typy argumentów:

Typ Składnia app:argType Obsługa wartości domyślnych Obsługiwane przez trasy Dopuszczalna wartość null
Liczba całkowita app:argType="integer" Tak Tak Nie
Pływające app:argType="float" Tak Tak Nie
Długie app:argType="long" Tak – wartości domyślne muszą zawsze 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="string" 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
Niestandardowe obiekty Parcelable app:argType="<type>", gdzie <type> to pełna i jednoznaczna nazwa klasy Parcelable Obsługuje wartość domyślną „@null”. Nie obsługuje innych wartości domyślnych. Nie Tak
Niestandardowa serializacja app:argType="<type>", gdzie <type> to pełna i jednoznaczna nazwa 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ą być zgodne z nazwą bezkwalifikowaną (np. „SUCCESS” musi być zgodne z MyEnum.SUCCESS). Nie Nie

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

Trasy, precyzyjne linki i identyfikatory URI wraz z ich argumentami mogą być analizowane na podstawie ciągów znaków. Nie jest to możliwe w przypadku niestandardowych typów danych, takich jak Parcelables i Serializables, jak widać w poprzedniej tabeli. Aby przekazywać niestandardowe dane złożone, przechowuj je w innym miejscu, np. w ViewModel lub bazie danych, i podczas nawigacji przekazuj tylko identyfikator. Następnie po zakończeniu nawigacji pobierz dane z nowej lokalizacji.

Po wybraniu jednego z typów niestandardowych pojawi się okno Wybierz zajęcia, w którym możesz wybrać zajęcia odpowiadające temu typowi. Na karcie Projekt możesz wybrać klasę z bieżącego projektu.

Możesz wybrać <inferred type>, aby biblioteka Nawigacja określiła typ na podstawie podanej wartości.

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

  • Tablice typów wyliczeniowych i tablice odwołań do zasobów nie są obsługiwane.
  • Tablice obsługują wartości opcjonalne niezależnie od obsługi wartości opcjonalnych typu podstawowego. Na przykład użycie funkcji app:argType="integer[]" pozwala użyć funkcji app:nullable="true", aby wskazać, że przekazanie tablicy null jest dopuszczalne.
  • Tablice obsługują jedną wartość domyślną, „@null”. Tablice nie obsługują żadnych innych wartości domyślnych.

Zastępowanie argumentu miejsca docelowego w działaniu

Argumenty i wartości domyślne na poziomie miejsca docelowego są używane przez wszystkie działania, które przekierowują do tego miejsca. 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 taki sam typ jak argument zadeklarowany w miejscu docelowym.

Ten kod XML deklaruje działanie z argumentem, który zastępuje argument dotyczący poziomu 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>

Przekazywanie danych z zabezpieczeniami typu za pomocą Safe Args

Komponent Nawigacja ma wtyczkę Gradle o nazwie Safe Args, która generuje proste klasy obiektów i klasy konstruktora na potrzeby bezpiecznej nawigacji typu oraz dostępu do wszystkich powiązanych argumentów. Zalecamy używanie Safe Args do nawigacji i przekazywania danych, ponieważ zapewnia to bezpieczeństwo typów.

Jeśli nie używasz Gradle, nie możesz korzystać z wtyczki Safe Args. W takich przypadkach możesz korzystać z pakietów, aby przekazywać dane bezpośrednio.

Aby dodać Safe Args do projektu, umieść w pliku najwyższego poziomu build.gradle ten element classpath:

Groovy

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

Kotlin

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

Musisz też zastosować jedną z 2 dostępnych wtyczek.

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

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

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

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

Groovy

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

Kotlin

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

W pliku gradle.properties musisz mieć wartość android.useAndroidX=true, zgodnie z informacjami w artykule Przenoszenie na AndroidX.

Po włączeniu opcji Bezpieczne argumenty wygenerowany kod zawiera te 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 występuje działanie. Nazwa tej klasy to nazwa miejsca docelowego z dołączonym słowem „Wskazówki”. Jeśli na przykład miejsce docelowe pochodzenia to fragment o nazwie SpecifyAmountFragment, wygenerowana klasa będzie się nazywać SpecifyAmountFragmentDirections.

    Ta klasa ma metodę dla każdego działania zdefiniowanego w pochodzącym miejscu docelowym.

  • Dla każdego działania używanego do przekazywania argumentu tworzona jest klasa wewnętrzna, której nazwa jest określana na podstawie działania. Jeśli na przykład działanie ma nazwęconfirmationAction,, klasa ma nazwę ConfirmationAction. Jeśli Twoje działanie zawiera argumenty bez parametru defaultValue, możesz użyć powiązanej klasy działania, aby ustawić wartość argumentów.

  • Dla docelowego miejsca docelowego zostaje utworzona klasa. Nazwa tej klasy to nazwa miejsca docelowego z dołączonym słowem „Args”. Jeśli na przykład fragment docelowy ma nazwę ConfirmationFragment,, wygenerowana klasa będzie się nazywać ConfirmationFragmentArgs. Aby pobrać argumenty, użyj metody fromBundle() tej klasy.

Z tego przykładu dowiesz się, jak użyć tych metod do ustawienia argumentu i przekazania 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. Użytkownicy Kotlina mogą też używać delegata właściwości by navArgs() do uzyskiwania dostępu do argumentów podczas korzystania z zależności -ktx.

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 przypadku działania globalnego

Jeśli używasz argumentów bezpiecznych w działaniu globalnym, musisz podać wartość android:id dla elementu <navigation> wyższego poziomu, jak 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 na przykład masz element <navigation> z wartością android:id=@+id/main_nav, wygenerowana klasa będzie się nazywać MainNavDirections. Wszystkie miejsca docelowe w elemencie <navigation> mają wygenerowane metody dostępu do wszystkich powiązanych działań globalnych za pomocą tych samych metod, co opisano w poprzedniej sekcji.

Przesyłanie danych między miejscami docelowymi za pomocą obiektów pakietu

Jeśli nie używasz Gradle, możesz nadal przekazywać argumenty między miejscami docelowymi, używając obiektów Bundle. Utwórz obiekt Bundle i przekaż go do miejsca docelowego za pomocą elementu 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ć plik Bundle i używać jego zawartości:

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"));

Przekazywanie danych do miejsca docelowego

Możesz przekazywać dane do miejsca docelowego startowego aplikacji. Najpierw musisz jawnie utworzyć obiekt Bundle, który będzie przechowywać dane. Następnie użyj jednej z tych metod, aby przekazać Bundle do miejsca docelowego:

Aby pobrać dane z miejsca docelowego, zadzwoń pod numer Fragment.getArguments().

Uwagi dotyczące ProGuard

Jeśli zmniejszasz rozmiar kodu, musisz zadbać o to, aby nazwy klas Parcelable, SerializableEnum nie zostały zaciemnione w ramach procesu minifikacji. Możesz to zrobić na 2 sposoby:

  • Używanie adnotacji @Keep.
  • Używaj reguł dotyczących zachowywania nazw.

W następnych podrozdziałach omawiamy te podejścia.

Używanie adnotacji @Keep

W tym przykładzie adnotacje @Keep dodajemy do definicji klasy 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ł dotyczących zachowywania nazw

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.

Ćwiczenia z programowania

Filmy