Navigation Componentの基本的な使い方

# 得られる知識

- 画面間での遷移方法
- データの受け渡し方法

# Navigation Componentとは
画面間の遷移の実装を楽にしてくれるGoogleが開発しているライブラリです。


# 環境
Android Studio4.0 Canary2

# 準備
## Navigation Componentを導入する
```build.gradle
dependencies {
    // 追加するものたち
    def nav_version = "2.1.0"

    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
```

# ナビゲーショングラフを作成する
ナビゲーショングラフとは、すべてのデスティネーション(ユーザがアプリ内で移動できる画面)とアクション（どの画面からどの画面に移動するか）を格納するリソースファイルです。

## リソースファイルの作成
`resディレクトリを右クリック > [New] > [Android Resource File]` を選択し、`New Resource Fileダイアログ`に以下の内容を入力します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/2e83aaa2-c9c5-8e16-2b68-8efcd1ebd3a0.png">

リソースファイルを作成すると、Navigation Editorが開きます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/cb55b812-ea0c-b117-5027-d1f8228f2afb.png">

## デスティネーションの作成
リソースファイルの作成ができたので、デスティネーションを作成していきます。

### 新規フラグメントからデスティネーションを作成する
Navigation Editorから`New Destinationアイコン`をクリックするとプルダウンメニューが表示されます。

<img width="1918" alt="スクリーンショット 2020-01-27 11.40.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/531adeac-da70-23b9-4ff8-d32b19942c91.png">

プルダウンメニューの`Create New Destination`をクリックすると、`New Android Fragment`が表示されるので、作成したいFragmentを選択してNextをクリックしてください。今回は、`Fragment(Blank)`を選択しました。

<img width="1918" alt="スクリーンショット 2020-01-27 11.42.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/46068918-22a6-24ea-645e-47160f1148d3.png">

フラグメントの名前、言語を決めてFinishをクリックしてください。
<img width="1918" alt="スクリーンショット 2020-01-27 11.43.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/be8658eb-5dd9-9de7-7485-a31a18bd767e.png">

Navigation Editorに戻ると作成したデスティネーションが追加されていると思います。

<img width="1918" alt="スクリーンショット 2020-01-27 12.47.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/cab8e8d5-15dc-9fcf-3da7-3f1e2b6285d6.png">

`nav_graph.xml`のコードはこのようになっています。

```xml:nav_graph.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/nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.example.navigationsample.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" />
</navigation>
```

### 既存のフラグメントからデスティネーションを作成する

Navigation Editorから`New Destinationアイコン`をクリックするとプルダウンメニューが表示されます。

プルダウンメニューの追加したいデスティネーションを選択します。今回は、前もって作成しておいたSecondFragmentを選択しました。

<img width="1918" alt="スクリーンショット 2020-01-27 12.43.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/d6d9703b-9cb9-8aa6-f443-01f04e1e63e3.png">

Navigation Editorに選択したデスティネーションが追加されていると思います。

<img width="1918" alt="スクリーンショット 2020-01-27 12.56.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/c2e74e57-1eeb-40e1-60e9-23d2d5ed1ddd.png">

`nav_graph.xml`のコードはこのようになっています。

```xml:nav_graph.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/nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.example.navigationsample.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" />
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationsample.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
</navigation>
```

## アクションの追加
次に、アクション（どの画面からどの画面に移動するか）を追加していきます。

Navigation Editorで移動元となるデスティネーションの右側にカーソルを合わせます。

<img width="1918" alt="スクリーンショット 2020-01-27 13.03.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/2fe8d367-4493-2bd7-494d-28eb477ae429.png">

クリックしたまま、遷移したいデスティネーションにカーソルをドラッグします。するとアクションが追加されて２つのデスティネーションを結ぶ線が表示されます。

<img width="1918" alt="スクリーンショット 2020-01-27 13.06.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/70d3b494-4b75-8f09-bcbf-0e188d947351.png">

`nav_graph.xml`をコードはこのようになっています。
FirstFragmentのデスティネーションに`<action>タグ`が追加されていて、アクションIDと遷移先のデスティネーションのIDがあります。

```xml:nav_graph.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/nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.example.navigationsample.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" >
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationsample.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
</navigation>
```

# NavHostをアクティビティに追加する
Navigation Componentは一つのアクティビティに対して、必要に応じて複数のフラグメントを切り替えて表示します。その切替を行ってくれるNavHostFragmentをアクティビティに追加していきます。`app:navgraph`でNavHostFragmentに先程作成したナビゲーショングラフを関連付けています。

```xml:activity_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">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"

        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

# 画面間での遷移方法
ナビゲーショングラフも作成できたところで、画面間での遷移方法を説明していきます。

今回は、FirstFragmentでボタンを押したら、SecondFragmentに遷移するようにしていきます。
まず、ボタンを追加するべくFirstFragmentのレイアウトを以下のように編集しました。

```xml:fragment_first.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=".FirstFragment">
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</androidx.constraintlayout.widget.ConstraintLayout>
```

FirstFragmentの画面は、以下のようになっていると思います。

<img width="30%" height=30% alt="スクリーンショット 2020-01-27 14.54.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/876ec863-5c52-8080-5ab1-074b219ce85e.png">

SecondFragmentに遷移するときは`Navigation.findNavController(view: View).navigate(遷移先のデスティネーションID)`を使います。

```kotlin:FirstFragment.kt
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val view =  inflater.inflate(R.layout.fragment_first, container, false)

    view.findViewById<Button>(R.id.button).setOnClickListener {
        // SecondFragmentに遷移するよ
        Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment2)
    }

    return view
}
```

# データの受け渡し方法
次に値の受け渡しの方法を説明していきます。

データの受け渡しは、Safe Argsと呼ばれるGradleプラグインを使うことを強く推奨されています。Safe Argsを使用すると型の安全性が保証されるためです。

まずは、Safe Argsを使用するためにプロジェクトに追加していきます。

```gradle:build.gradle(Project)
dependencies {
        // 追加するもの
        def nav_version = "2.1.0"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
```

```gradle:build.gradle(Module)
apply plugin: "androidx.navigation.safeargs.kotlin"
```

ナビゲーショングラフに渡す値を設定していきます。
Navigation Editorから遷移先のデスティネーションを指定して、Argumentsタブの`+アイコン`をクリックします。

Add Argumentダイアログがでてくるので渡す値の名前と型を入力してください。
<img width="1914" alt="スクリーンショット 2020-01-27 15.50.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434603/db165032-1c36-9cfc-a676-3f38fc7ea3d1.png">

追加するとnav_graph.xmlが以下のようになっていると思います。

```xml:nav_graph.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/nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.example.navigationsample.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first">
        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationsample.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second">

        <argument
            android:name="userName"
            app:argType="string" />
    </fragment>
</navigation>
```

Safe Argsを使用しているとFirstFragmentDirectionsというクラスとSecondFragmentArgsというクラスが作成されます。

遷移元のフラグメントから値を渡す時は、以下のようにしてあげます。

```kotlin:FirstFragment.kt
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val view =  inflater.inflate(R.layout.fragment_first, container, false)

    view.findViewById<Button>(R.id.button).setOnClickListener {
        val action = FirstFragmentDirections.actionFirstFragmentToSecondFragment("fu_mirai")

        Navigation.findNavController(it).navigate(action)
    }

    return view
}
```

遷移先のフラグメントで値を受け取る時は、以下のようにしてあげます。

```kotlin:SecondFragment.kt
val args: SecondFragmentArgs by navArgs()

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val userName = args.userName
    
    return inflater.inflate(R.layout.fragment_second, container, false)
}
```

# 終わりに
今回は、Navigation Componentについて説明していきました。

画面間の遷移が簡単に実装できたり、値の受け渡しがシンプルになるのがとてもよかったです。また、Android Studio上でどの画面からどの画面に遷移しているのかがわかるのも良きでした。
ディープリンクなども簡単に設定できたりするらしいので試していきたいなと思います。