Navigation Componentの使い方（概要〜画面遷移〜データの受け渡し編）

# はじめに
Jetpackで追加された、 **Navigation Component** を調べたいな〜とずっと考えていましたが、ようやく使い方が理解できたので使い方をこの記事では説明します。

# 動作環境
この記事の動作環境は以下のとおりです。

Android Studio：4.1.1
Kotln：1.4.21
Open JDK:1.8
compileSdkVersion：30
targetSdkVersion：30
minSdkVersion：23
Navigation Component:2.3.2

# 目標
以下を目標とします。

* 基本的な利用方法を理解する
* 画面遷移方法を理解する
* 画面間でデータの受け渡しを行う方法を理解する

# 動作イメージ
この記事の最終動作イメージは下図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/5b48de9b-99ea-4d8a-6dbe-1dcd76a0e27b.gif" width="40%"/>


# サンプルコード
早くサンプルコード見せろって方は下記からダウンロードしてください。

[github](https://github.com/ktaro-nakaya/qiita-navigation-component)

# Navigation Componentの概要
まずはNavigation Componentって何？？？って話をしていきます。
Androidアプリ開発において、画面遷移は全体像を理解するために、各画面を作成してからOfficeなどを使い、画面遷移図を作成しているかと思います。
iOSアプリ開発ではStoryboardが存在し、IDE上で視覚的に画面遷移が理解出来ます。
Androidアプリ開発でも、Storyboardのように視覚的に直感で画面遷移が理解出来ます。
それだけではなく、メリットがあります。[公式サイト](https://developer.android.com/guide/navigation)ではその他にも以下のメリットを上げていますね。

* フラグメント トランザクションを処理する。
* デフォルトで「上」アクションと「戻る」アクションを正しく処理する。
* アニメーションと遷移用の標準化されたリソースを提供する。
* ディープリンクを実装して処理する。
* 最小限の追加作業で、ナビゲーション ドロワーやボトム ナビゲーションなどのナビゲーション UI パターンを組み込める。
* Safe Args - 宛先間で移動とデータの受け渡しを行う際に型の安全性を提供する Gradle プラグイン。
* ViewModel のサポート - ViewModel のスコープをナビゲーション グラフに設定して、グラフの宛先間で UI 関連のデータを共有できます。


なかなか、いろんな事が簡単に実現できそうな感じです。
フラグメントのトランザクション管理をやってくれるのは本当にありがたいです。
ということは、Navigation Componentってのは、どうやらFragmentを使って実現しているようです。
フラグメントで最低限の知識がないかも不安って方は[Fragmentの基本](https://qiita.com/naoi/items/3e1125d1e1418d09f77a)を参照してください。


# 実装方法
## 必要なファイル群
それでは、実装方法について説明します。
実装するにも、何から手をつけてよいやらと思います。
Navigation Componentで必要となるActivityやFragmentを図にしてみました。

![01_navigation_componentの概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/8a150657-4276-b214-2395-eff4571708fa.png)


以下のファイル群が登場します。

* navigationの設定ファイル（XML）
* Activity
* Fragment

それぞれの役割をまとめるとこの様な感じです。

|ファイル|役割|
|:----|:----|
|Activity|Fragmentのコンテナが基本的な役割だが、アクションバーやツールバー、ナビゲーションドロワーの制御を行います。|
|Fragment|実際に表示する画面や機能を持つ|
|navigationの設定ファイル（XML）|画面遷移や画面間のデータの受け渡しの設定、画面遷移のアニメーションの設定などを管理する|

navigationの設定ファイルで画面遷移を表現すると言ったところです。
内部的にはActivityのコンテナ部分でFragmentを操作しているようです。

## Fragmentを準備する
必要なファイル群を理解すると、navigationの設定ファイルを作る前にFragmentなどを事前に準備しておく必要があることが分かります。

まずはBlankでもいいので、Fragmentを3ほど用意してみます。

![02_Fragmentの準備.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/db84508e-000a-d09c-c322-c1f6e2adf6de.png)


今回は以下の３つのFragmentを用意しました。そのぞれの画面の役割も説明します。

|Fragment名|役割|
|:----|:----|
|StartFragment|名前が入力でき、FirstFragmentやSecondFragmentに遷移するボタンを保持する。入力した名前をFirstFragmentやSecondFragmentに受け渡す|
|FirstFragment|StartFragmentから画面遷移してきて、入力した名前を受け取る。受け取った名前を表示する|
|SecondFragment|FirstFragmentと同じくStartFragmentから画面遷移してきて、入力した名前を受け取る。受け取った名前の文字数を表示する|

これで必要なFragmentが準備できました。

## navigationの設定ファイル作成
Fragmentが準備できたらnavigationの設定ファイルを作成します。
これはAndroid StudioのGUI操作でサクサク作れます。
手順は以下のとおりです。

 1. **res**を右クリック
 2. **Resource Type**を **Navigation** を選択。
 3.  **name** にファイル名を入力、今回は **my_navi** を入力
 3. 必要なライブラリーがbuild.gradleのdependenciesに存在しない場合、ダイアログが表示されるため、OKを押下
 4.  **res** フォルダに **navigation** フォルダが作成され、my_navi.xmlファイルが出来ている事を確認する

実際の操作画面は下図です。

![03_navigationの設定ファイル（XML）の作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/0af1ce54-7755-82da-fa3d-ae91a294fdbb.png)


家マークが付いているFragmentは、初期画面を意味します。初期画面の設定は画面上部の家マークで変更可能です。

## Fragmentをnavigation設定でFragmentの追加と接続

navigationの設定ファイルが作成したら、いよいよ、Fragmetの画面遷移をGUIで表していきます。

### 画面（Destination：デスティネーション）の追加
まずは、navigationの設定ファイルにFragmentを追加しましょう。
手順は以下のとおりです。

1.  **new destination**ボタンをクリック
2.  対象のFragmentをクリック

Navigation Componentでは、Fragmentの事をDestination（デスティネーション）と呼ぶようです。 

今回は以下の３つを追加します。

* StartFragment
* FirstFragment
* SecondFragment

実際の操作イメージは以下のとおりです。

![04_destienationの追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/a396844a-a3c3-9014-d77d-c5cfe37fe7b7.png)

追加が完了すると左下のComponent Treeに追加したデスティネーション（画面）が表示されます。

### 画面遷移の線（Action：アクション）を接続
デスティネーション（画面）が追加できたら、画面遷移の線で表します。
線のことを **アクション** と呼びます。
アクションの接続手順は以下のとおりです。

1. 遷移元のデスティネーション（画面）の右端に表示する○から遷移先にドラッグアンドドロップします。

実際の操作イメージは下図の通りです。

![05_destinationをつなげる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/4426df4a-457c-9c8f-e71d-62a83aa3f49c.png)

接続が完了するとComponetTreeにもアクションが表示されます。

## Activityの設定
navigationの設定ファイルがある程度完成したら、Activityのコンテナを設定して作成したNavigation Componentを設定していきます。

手順は以下のとおりです。
1. Activityのレイアウトファイルを開く
2. プロジェクト作成時に生成されたTextViewなどは削除しておく
3. デザインタブが選択された状態にする
4.  **Palette** の検索で **fragment** と入力
5.   **NavHostFragment** を画面にドラッグアンドドロップする
6.    **NavHostFragment** の選択ダイアログが表示したら、作成したnavigation設定ファイルのmy_naviを選択する


実際の操作イメージは下図の通りです。

![06_activityへの適応.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/ec3384b1-b9b4-7864-8227-cbae84e56cd4.png)


**NavHostFragment** を追加した後にCodeタブで確認をするとFragmentに警告が出ています。どうやら、 **FragmentContainerView** に変更すべし！！と出ているので修正します。
FragmentContainerViewについてはご自身で調べてください。

以下のように修正されていれば問題ありません。

![07_Fragmentタグの修正.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/bfcce4bc-f664-2d05-196a-af52c24fddc5.png)


ここまで設定が出来たら、動作確認を行ってみました。

 **StartFragment** の画面が下図のように表示されれば問題ありません。
この時点で、画面遷移は起きません。
なぜなら、画面遷移の処理を記述する必要があるからです。

![08_動作確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/63b7b7d4-0082-32f2-f4f8-ad4cfd6d6e18.png)


## 画面遷移
画面遷移するためには処理の記述が必要です。どの様な処理を記述するのかを公式サイトで調べてみると、以下の手順になるようです。

1. NavHostを取得
2. NavHostから以下のいずれかでNavControllerを取得
    3. Fragment.findNavController()
    4. View.findNavController()
    5. Activity.findNavController(viewId: Int)
6. アクションを取得
6. navigateでアクションを指定し画面遷移

ここでは、以下の２つが重要な役割を果たしているようです。

* NavHost
* NavController

それぞれの役割の概要は以下のとおりです。

|クラス名|役割|
|:----|:----|
|NavHost|Navigation用の単一コンテナ。直接操作せずに、NavControllerを通じて操作する。NavControllerを渡すメソッドを保持している。|
|NavController|NavHostを保持し、アプリケーションのNavigationを管理する。NavControllerが保持しているデスティネーションはナビゲーショングラフ（navigation設定ファイル）によって異なる|

どうやら、画面遷移にはNavControllerで行う事がわかります。

では、NavHostを取得しようと考えたら、Navigation Componentを利用するのであれば、 **Safe Args** を利用して、タイプセーフに移動することを推奨しているようです。
 **Safe Args** を利用するとAndroid Studioの入力補完も効くので、オススメです。

 **Safe Args** を利用するにはbuild.garadleに以下の修正を加える必要があります。

```groovy:build.gradle（project）
dependencies {
    def nav_version = "2.3.2"
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
```


```groovy:build.gradle(app)
apply plugin: "androidx.navigation.safeargs.kotlin"
```
appのbuild.gradleですが、プロジェクトによっては以下のように修正する必要があります。

```groovy:build.gradle(app)
plugins {
    id 'androidx.navigation.safeargs.kotlin'
}
```

ここまで出来たら、ビルドしておきます。
公式サイトには、pluginを追加すると遷移元のデスティネーションからクラスが自動生成されるとのことですが、実際はされないのでビルドし直してAndroid Studioに生成してもらいます。

リビルドは下図の手順で行ってください。

![09_リビルド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/939c9c2b-40ac-c0fa-3577-01a90150a31a.png)


準備が整ったので、画面遷移を実装していきます。
今一度、手順を振り返っておきます。

1. NavHostを取得
2. NavHostから以下のいずれかでNavControllerを取得
    3. Fragment.findNavController()
    4. View.findNavController()
    5. Activity.findNavController(viewId: Int)
6. アクションを取得
6. navigateでアクションを指定し画面遷移

 **NavHost** を取得するとあります。しかし、ここまで、 **NavHost** なんてものを定義した記憶はありません。
実は、Activityのコンテナに設定しています。
Activityのレイアウトリソースファイルを見てみます。

```xml:actitivy_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/my_navi"
        />
</androidx.constraintlayout.widget.ConstraintLayout>
```

FragmentContainerViewの **android:name** 属性を確認してみると、しっかり ** NavHostFragment** って記述がありますね。
どうやら、Activityにコンテナを設定したときにAndroid Studioが自動で設定してくれたみたいです。 **app:navGraph** 属性には、navigationの設定ファイルが指定されていますね。
ここで紐付いているようです。
なるほど、しかも公式サイトにはこんな記述があります。

> FragmentContainerView を使用して NavHostFragment を作成する場合、または FragmentTransaction からアクティビティに NavHostFragment を手動で追加する場合、Navigation.findNavController(Activity, @IdRes int) からアクティビティの onCreate() で NavController を取得しようとするとエラーになります。NavHostFragment から直接 NavController を取得してください。

フラグメントを取得してそのままキャストすればいいとのことです。
なるほど、われわれはこうなるわけですね。

```kotlin:StartFragment.kt
// NavHostの取得
val navHostFragment =
    requireActivity().supportFragmentManager.findFragmentById(R.id.host_fragment) as NavHostFragment
```

次にNavControllerを取得します。NavControllerはNavHostから取得するので下記の記述になります。

```kotlin:StartFragment.kt
// NavController取得
val navController = navHostFragment.navController
```
続いてActionを取得しましょう。

公式サイトに下記の記述があります。

> Safe Args を有効にすると、このプラグインによって、定義した各アクションのクラスとメソッドを格納するコードが生成されます。

そうでしたね。自動でクラスを生成してくれるのでした。
アクションはその線の元に内部クラスとして生成されます。
今回は、StartFragmentから線を延ばしました。そのため、自動生成されたStartFragmentのクラスから取得します。
自動生成されるクラス名は以下のとおりです。

まずはFragmentのクラス名は以下のとおりです。

Fragmentのクラス名 +  **Directions** 

なので、StartFragmentは **StartFragmentDirections** という名前のクラスが生成されます。そしてそのクラスの内部クラスとしてActionのクラスが生成されます。

Actionのクラス名はnavigationの設定ファイルで指定したActionのid属性から生成されます。
変換ルールは「スネークケース→ローワーキャメルケース」
navigationの設定ファイルをcodeタブで確認すると下記のようになっています。

```xml:my_navi.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_navi"
    app:startDestination="@id/startFragment">

    <fragment
        android:id="@+id/startFragment"
        android:name="jp.co.casareal.navigationcomponent_sample.StartFragment"
        android:label="fragment_start"
        tools:layout="@layout/fragment_start" >
        <action
            android:id="@+id/action_startFragment_to_firstFragment"
            app:destination="@id/firstFragment" />
        <action
            android:id="@+id/action_startFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
</navigation>
```

今回の場合は以下のように変換されます。

 **action_startFragment_to_firstFragment** →  **ActionStartFragmentToFirstFragment** 

変換ルールを理解したところで、実際にActionを取得するコードを確認します。
Actionを取得するには、idをローワーキャメルケースのメソッドを呼び出します。

```kotlin:StartFragment.kt
val action =
    StartFragmentDirections.actionStartFragmentToFirstFragment()
```

Actionが取得できたら、いよいよ、最後の画面遷移です。
NavControllerのnavigateメソッドの引数にActionのオブジェクトを渡して呼び出すだけです。

```kotlin:StartFragment.kt
navController.navigate(action)
```

この時点で動作確認をすると、入力したデータを遷移先の画面に受け渡しができてないですが、しっかり画面遷移できます。

![10_画面遷移.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/7b4c4307-033e-ef6d-25a6-1fe1ae54af5a.png)


## 画面間のデータの受け渡し
Navigation Componentで画面間のデータの受け渡しはいくつかやり方があります。
事前に **Safe Args を有効** しているため、navigationの設定ファイルにデータを受け渡す設定をします。

データの受け渡しのイメージとしては、遷移先のFragmentに変数を設定し、Actionを取得する際にその変数に値を格納するイメージです。

![11_データの受け口.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/7d413e77-ee33-dd9c-63e0-93ef26612a8f.png)


設定ファイルはXMLなので、コードで編集も可能ですが、GUIでも設定が可能です。
手順は以下のとおりです。

1. GUIでせっていする場合は、navigationの設定ファイルを開く
2. デザインタブを選択する
3. データを受け取る **（遷移先の）Fragment** を選択する
4.  **Arguments** の「＋」ボタンをクリック
5.  表示されたダイアログの **name**を入力する
6.   **type** を選択する


実際の画面間イメージは下図の通りです。

![12_データの受け口の設定方法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/482ce78c-074b-4207-da96-e30628713bb6.png)


Codeタブを開くと下図のように遷移先のFragmentに **argument** タグが定義されます。

```xml:my_navi.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_navi"
    app:startDestination="@id/startFragment">
	<fragment
        android:id="@+id/firstFragment"
        android:name="jp.co.casareal.navigationcomponent_sample.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" >
        <argument
            android:name="nameForConfirm"
            app:argType="string"/>
    </fragment>
</navigation>
```

受け渡しできるデータの方は下記の通りです。
詳細は[公式サイト](https://developer.android.com/guide/navigation/navigation-pass-data?hl=ja)を確認してください。

|型|記述例|
|:---|:---|
|Integer|app:argType="integer"| 
|Float|app:argType="float"| 
|Long|app:argType="long"| 
|Boolean|app:argType="boolean"| 
|String	app:argType="string"| 
|参照|app:argType="reference"| 
|Parcelable	app:argType="<type>"| 
|Serializable|app:argType="<type>"| 
|Enum|app:argType="<type>"|

大体の型には対応しています。

遷移先にデータを受け渡すコードを記述していきます。
Actionを取得する際に、呼び出すメソッドに受け渡すデータを設定します。

```kotlin:StartFragment.kt
val action =
    StartFragmentDirections.actionStartFragmentToFirstFragment(nameForConfirm = strName)
```

 最後にデータを受け取る処理です。
navigationの設定ファイルにargmentタグを追加すると、設定したFragmentのクラスから以下のルールでクラスが自動生成されます。

フラグメントのクラス名 +  **Args**

 **FirstFragment** であれば **FirstFragmentArgs** となります。
 
実際に取得するには **navArgs** 関数を呼び出します。

```kotlin:FirstFragment.kt
class FirstFragment : Fragment() {

    // データが格納されているargsを取得
    private val args:FirstFragmentArgs by navArgs()

}

```

 **args:FirstFragmentArgs** が取得できればあとは簡単です。
FirstFragamentArgsのプロパティにnavigationの設定ファイルのargmentのname属性で指定した名前で追加されています。

実際のコードは下記のとおりです。

```kotlin:FirstFragment.kt
args.nameForConfirm
```


以上でデータの受け渡しが完了です。

# まとめ
Navigation Componentの基本的な利用方法は多くあるため、覚えるのだけでも一苦労です。
しかし、他にも機能があるのでしっかり覚えておくことをおすすめします。