在目标之间传递数据

导航允许您通过为目标定义参数将数据附加到导航操作。例如,用户配置文件目标可能会采用用户 ID 参数来确定要显示哪个用户。

通常,您应该强烈建议仅在目标之间传递最少的数据量。例如,您应该传递一个键来检索对象,而不是传递对象本身,因为 Android 上所有保存状态的总空间是有限的。如果您需要传递大量数据,请使用 ViewModel,如 ViewModel 概述 中所述。

定义目标参数

要在线程之间传递数据,首先通过按照以下步骤将其添加到接收它的目标来定义参数

  1. 导航编辑器 中,单击接收参数的目标。
  2. 在“属性”面板中,单击“添加”(+)。
  3. 在出现的“添加参数链接”窗口中,输入参数名称、参数类型、参数是否可为空以及在需要时输入默认值。
  4. 单击“添加”。请注意,参数现在显示在“属性”面板中的“参数”列表中。
  5. 接下来,单击将您带到此目标的相应操作。在“属性”面板中,您现在应该在“参数默认值”部分看到您新添加的参数。
  6. 您还可以看到参数已在 XML 中添加。单击“文本”选项卡以切换到 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" 声明 null 的默认值。

可以从字符串中解析路由、深层链接和 URI 及其参数。这在使用自定义数据类型(如上表中所示的 Parcelables 和 Serializables)时是不可能的。要传递自定义复杂数据,请将数据存储在其他位置(如 ViewModel 或数据库)中,并在导航时仅传递标识符;然后在导航完成后在新的位置检索数据。

当您选择其中一种自定义类型时,将出现“选择类”对话框,并提示您为该类型选择相应的类。“项目”选项卡允许您从当前项目中选择一个类。

您可以选择“<推断类型>”以让导航库根据提供的 value 确定类型。

您可以选中“数组”以指示参数应为所选“类型”值的数组。请注意以下事项

  • 不支持枚举数组和资源引用数组。
  • 数组支持可为空的值,无论底层类型的可为空值支持如何。例如,使用 app:argType="integer[]" 允许您使用 app:nullable="true" 来指示传递 null 数组是可以接受的。
  • 数组支持单个默认值“@null”。数组不支持任何其他默认值。

在操作中覆盖目标参数

目标级别的参数和默认值被导航到该目标的所有操作使用。如果需要,可以通过在操作级别定义一个参数来覆盖参数的默认值(或者如果没有则设置一个)。此参数必须与目标中声明的参数具有相同的名称和类型。

以下XML声明了一个操作,该操作包含一个参数,该参数覆盖了前面示例中的目标级参数

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

使用Safe Args以类型安全的方式传递数据

Navigation组件有一个名为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")
}

您必须在您的gradle.properties文件中包含android.useAndroidX=true,如迁移到AndroidX中所述。

启用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()方法检索bundle并使用其内容。当使用-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>

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注释。
  • 使用keepnames规则。

以下小节概述了这些方法。

使用@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 { ... }

使用keepnames规则

您还可以将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

...

其他资源

要了解有关导航的更多信息,请参阅以下其他资源。

示例

Codelabs

视频