在目的地之間傳遞資料

導覽可為目的地定義引數,將資料附加至導覽作業。舉例來說,使用者設定檔目的地可能會利用使用者 ID 引數來判斷要顯示的使用者。

一般而言,在目的地之間傳遞的資料量越低越好。例如,您應該傳遞擷取物件的金鑰,而不是傳遞物件本身,因為 Android 上用於所有儲存狀態的總空間有限。如果您需要傳遞大量資料,請使用 ViewModel 簡介中所述的 ViewModel

定義目的地引數

如要在目的地之間傳遞資料,請先按照下列步驟將引數加入接收目的地的引數,藉此定義引數:

  1. 導覽編輯器中,按一下接收引數的目的地。
  2. 在「Attributes」面板中按一下「Add」 (+)。
  3. 在出現的「Add Argument Link」視窗中輸入引數名稱、引數類型、引數是否可為空值和預設值 (如有需要)。
  4. 按一下「Add」。請注意,引數現在會顯示在「Attributes」面板的「Arguments」清單中。
  5. 接著,按一下會將您帶至此目的地的對應動作。在「Attributes」面板中,現在應該會在「Argument Default Values」部分看到新增的引數。
  6. 您也可以看到引數新增到 XML 中。按一下「Text」分頁標籤切換至 XML 檢視畫面,就會發現引數已新增至接收該引數的目的地。以下範例說明:

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

支援的引數類型

導覽程式庫支援下列引數類型:

類型 app:argType 語法 支援預設值 由路徑處理 可以為空值
整數 app:argType="integer"
浮點數 app:argType="float"
長整數 app:argType="long" 是 - 預設值一律必須以「L」結尾 (例如「123L」)。
布林值 app:argType="boolean" 是 - 「true」或「false」
字串 app:argType="string"
資源參照 app:argType="reference" 是 - 預設值必須是「@resourceType/resourceName」格式 (例如「@style/myCustomStyle」) 或「0」
自訂 Parcelable app:argType="<type>",其中 <type> 是 Parcelable 的完整類別名稱 支援預設值「@null」。不支援其他預設值。
自訂 Serializable app:argType="<type>",其中 <type> 是 Serializable 的完整類別名稱 支援預設值「@null」。不支援其他預設值。
自訂列舉 app:argType="<type>",其中 <type> 是列舉的完整名稱 是 - 預設值必須與不合格名稱對應 (例如「SUCCESS」對應 MyEnum.SUCCESS)。

如果引數類型支援空值,您可以使用 android:defaultValue="@null" 宣告預設空值。

您可以從字串剖析路徑、深層連結和 URI 及其引數,但無法使用 Parcelable 和 Serializable 等自訂資料類型,如上表所述。如要傳遞自訂複雜資料,請將資料儲存在其他位置 (例如 ViewModel 或資料庫),且只在導覽時傳遞 ID,然後在導覽後在新位置擷取資料。

當您選擇其中一種自訂類型時,系統會顯示「Select Class」對話方塊,並提示您選擇該類型對應的類別。「Project」分頁可讓您從目前的專案中選擇類別。

您可以選擇 <inferred type>,讓導覽程式庫根據提供的值判斷類型。

您可以勾選「Array」,表示引數應為所選「Type」值的陣列。請注意:

  • 不支援列舉陣列和資訊參照陣列。
  • 無論基礎類型是否支援可為空值的值,陣列提供支援該值。例如,使用 app:argType="integer[]" 時,您可以利用 app:nullable="true" 表示可以接受傳遞空值陣列。
  • 陣列支援單一預設值「@null」。陣列不支援任何其他預設值。

覆寫動作中的目的地引數

所有導覽至目的地的動作都會使用目的地層級的引數和預設值。如有需要,您可以在動作層級定義引數,覆寫引數的預設值;如果沒有引數,您也可以設定一個。這個引數的名稱和類型必須與目的地中宣告的引數相同。

下列 XML 宣告使用引數覆寫上述範例所示目的地層級引數的動作:

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

使用 Safe Args 傳遞類型安全的資料

導覽元件具備稱為 Safe Args 的 Gradle 外掛程式,可產生簡單的物件和建構工具類別,用於對關聯的引數進行類型安全存取。強烈建議您使用 Safe Args 進行導覽和傳遞資料,才可以確保類型安全。

如果您使用的不是 Gradle,則無法使用 Safe Args 外掛程式。在這種情況下,您可以使用 Bundle 直接傳遞資料。

如要在專案中新增 Safe Args,請在頂層 build.gradle 檔案中納入下列 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")
    }
}

您也必須從兩個可用的外掛程式之中選擇一項套用。

如要產生適合 Java 或混用 Java 及 Kotlin 模組的 Java 語言程式碼,請將以下這一行新增至應用程式或模組build.gradle 檔案:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

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

或者,如要產生適合 Kotlin 模組的 Kotlin 程式碼,請加入:

Groovy

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

Kotlin

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

根據遷移至 AndroidX 一文,gradle.properties 檔案中必須含有 android.useAndroidX=true

啟用 Safe Args 之後,產生的程式碼會包含下列個別動作的類型安全類別和方法,以及每個傳送和接收的目的地。

  • 系統會為每個動作最初的目的地建立類別。此類別的名稱是來源目的地的名稱加上「Directions」。例如,如果來源目的地是名為 SpecifyAmountFragment 的片段,產生的類別名稱為 SpecifyAmountFragmentDirections

    此類別會為來源目的地中定義的每個動作提供方法。

  • 針對用來傳遞引數的每個動作,都會建立一個內部類別,其名稱根據動作而定。例如,如果動作稱為 confirmationAction,,類別則稱為 ConfirmationAction。如果您的動作包含不含 defaultValue 的引數,您可以使用關聯的動作類別來設定引數值。

  • 系統會為接收目的地建立類別。此類別的名稱是目的地的名稱加上「Args」。例如,如果目的地片段的名稱是 ConfirmationFragment,,則產生的類別名稱為 ConfirmationFragmentArgs。請使用此類別的 fromBundle() 方法擷取引數。

以下範例說明如何使用這些方法來設定引數,並將其傳遞至 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);
}

在接收目的地的程式碼中,使用 getArguments() 方法擷取組合並使用其中的內容。使用 -ktx 依附元件時,Kotlin 使用者也可以使用 by navArgs() 屬性委派來存取引數。

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

結合全域動作使用 Safe Args

使用 Safe Args 搭配全域動作 時,您必須為根層級值 <navigation> 元素提供 android:id 值,如以下範例所示:

<?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>

導覽針對以 android:id 值為根據的 <navigation> 元素產生 Directions 類別。例如,如果您有一個帶有 android:id=@+id/main_nav<navigation> 元素,產生的類別名稱為 MainNavDirections<navigation> 元素中的所有目的地都已產生方法,以上一節所述的方法存取所有關聯的全域動作。

使用 Bundle 物件在目的地之間傳遞資料

如果您使用的不是 Gradle,仍然可以使用 Bundle 物件,在目的地之間傳遞引數。建立 Bundle 物件,並使用 navigate() 將其傳遞至目的地,如以下範例所示:

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

在接收目的地的程式碼中,使用 getArguments() 方法擷取 Bundle 並使用其中的內容:

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

將資料傳遞到起始目的地

您可以將資料傳遞到應用程式的起始目的地。首先,您必須明確建構保存資料的 Bundle。接下來,請使用下列其中一種方法,將 Bundle 傳遞至起始目的地:

如要擷取起始目的地中的資料,請呼叫 Fragment.getArguments()

ProGuard 注意事項

如果您要縮減程式碼,必須防止 ParcelableSerializableEnum 類別名稱在壓縮過程中混淆您可以選擇使用以下其中一種方法:

  • 使用 @Keep 註解。
  • 使用 keepname 規則。

以下各小節將概略說明這些做法。

使用 @Keep 註解

以下範例會將 @Keep 備註新增至模型類別定義:

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 { ... }

使用 keepname 規則

此外,您也可以將 keepnames 規則新增至 proguard-rules.pro 檔案,如以下範例所示:

proguard-rules.pro

...

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

...

其他資源

如要進一步瞭解導航,請參閱下列其他資源。

程式碼研究室

影片