デスティネーション間でデータを渡す

Navigation では、デスティネーションの引数を定義して、ナビゲーション オペレーションにデータをアタッチできます。たとえば、ユーザー プロフィールのデスティネーションでは、ユーザー ID 引数を受け取って、表示するユーザーを決定します。

一般に、デスティネーション間で渡すデータの量は、最小限に抑えることを強くおすすめします。たとえば、オブジェクト自体を渡すのではなく、オブジェクトを取得するためのキーを渡すようにします。これは、Android で保存済み状態のデータに使用できる合計容量が限られているためです。大量のデータを渡す必要がある場合は、ViewModel の概要で説明されているように ViewModel を使用します。

デスティネーション引数を定義する

デスティネーション間でデータを渡すには、まず、引数を定義します。そのためには、次の手順に沿って、引数を受け取るデスティネーションに引数を追加します。

  1. Navigation Editor で、引数を受け取るデスティネーションをクリックします。
  2. [Attributes] パネルで [Add]([+])をクリックします。
  3. 表示された [Add Argument Link] ウィンドウで、引数名、引数の型、引数に null を指定できるかどうか、デフォルト値(必要な場合)を入力します。
  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>
    

サポートされている引数の型

Navigation ライブラリでは、以下の引数の型がサポートされています。

タイプ app:argType の構文 デフォルト値のサポート ルートでの処理 null 許容
Integer app:argType="integer" はい はい ×
Float app:argType="float" はい はい ×
Long app:argType="long" ○ - デフォルト値は必ず接尾辞「L」で終わる必要があります(例: 「123L」)。 はい ×
Boolean app:argType="boolean" ○ - 「true」または「false」。 はい ×
String 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」をサポートしています。他のデフォルト値はサポートしていません。 × はい
カスタム Enum app:argType="<type>"(<type> は Enum の完全修飾名) ○ - デフォルト値が非修飾名と一致する必要があります(例: 「SUCCESS」は MyEnum.SUCCESS と一致)。 × ×

引数の型が null 値をサポートしている場合は、android:defaultValue="@null" を使用して null のデフォルト値を宣言できます。

ルート、ディープリンク、引数付き URI は、文字列から解析できます。これは、上記の表に示すように、Parcelable や Serializable などのカスタムデータ型を使用すると不可能です。複雑なカスタムデータを渡すには、ViewModel やデータベースなど別の場所にデータを格納し、ナビゲーション中は ID のみを渡して、ナビゲーションが完了した後に新しい場所でデータを取得します。

カスタム型のいずれかを選択した場合、[Select Class] ダイアログが表示され、その型に対応するクラスを選択するよう促されます。[Project] タブでは、現在のプロジェクトからクラスを選択できます。

<inferred type> を選択すると、Navigation ライブラリにより、指定した値に基づいて型が決められます。

引数に [Type] で選択した型の配列を使用する必要があることを示すには、[Array] チェックボックスをオンにします。次の点に注意してください。

  • Enum 型とリソース参照型の配列はサポートされていません。
  • 選択した型で null 値を使用できるかどうかにかかわらず、配列には null 値を指定できます。たとえば、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 は、ナビゲーションや関連引数へのアクセスをタイプセーフに行うためのシンプルなオブジェクトとビルダークラスを生成します。Safe Args は型安全性が確保されているため、データの移動と受け渡しには強く推奨されます。

Gradle を使用していない場合は、Safe Args プラグインを使用できません。そのような場合は、Bundle を使用することで、データを直接渡すことができます。

Safe Args をプロジェクトに追加するには、最上位の build.gradle ファイルに次の classpath を含めます。

Groovy

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

また、使用可能な 2 つのプラグインのいずれかを適用する必要があります。

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>

Navigation コンポーネントは、<navigation> 要素向けに android:id 値に基づく Directions クラスを生成します。たとえば、<navigation> 要素に対して android:id=@+id/main_nav が指定されている場合、「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 のクラス名が難読化されないようにする必要があります。これには次の 2 つの方法があります。

  • @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

...

参考情報

ナビゲーションについて詳しくは、以下の参考情報をご確認ください。

サンプル

Codelab

動画