Navigationを使ったアプリ開発

# はじめに

Navigationは、Android Jetpackに含まれているコンポーネントで、FragmentやActivityを用いた画面遷移を視覚的に管理することができます。
画面遷移を視覚的に管理することができ、遷移時の値渡しを型安全で渡すことができるという特徴などもあるので、使い方を確認してみようと思います。

# Navigationの特徴

Navigationには、以下のような特徴があります。

- Fragmentで画面遷移を実装する場合は、FragmentTransactionの処理をNavigationが行ってくれる。
- 進む、戻るのアクションを正しく処理することができる。
- Navigation Graphを使用し、画面遷移を視覚的に管理することができる。
- Navigation Graphは、Navigation Editorで簡単に作成・管理することができる。
- 遷移する際のアニメーションを簡単に設定することができる。
- Navigation中は型安全で値を渡すことができる。

基本的には、Intentで行っていた画面遷移の管理と同じことを行うことができるようです。

# Navigationの原則

Navigationには以下の5つの原則があります。
Navigationは、この原則を守って使用する必要があります。
原文は、[こちら](https://developer.android.com/topic/libraries/architecture/navigation#principles)です。

- 開始する画面は必ず固定されている必要がある。
- 画面は、Navitaion stackを介して表示される必要がある。
- Upボタンでアプリを終了させてはいけない。
- アプリ内のタスクでは、UPボタンもBackボタンも同一の動きをする。
- 同じ宛先へのディープリンク及びナビゲーションは常に同じスタックを生成する。


# Navigationコンポーネントに含まれるアイテム

Navigationコンポーネントには、Navigationを使用するためのいくつかのアイテムが含まれています。
詳細は実際にNavigationを使用するまでの手順中にまとめるているので、この章では各アイテムの概要だけを説明します。

## Navigation Graph

NavigationGraphは、Navigtionに含まれる画面や、画面遷移などがまとめられたファイルです。
このファイルは、resフォルあ配下にnavigationフォルダを作成し、そのフォルダ内にxmlファイルを作成し定義します。

## NavHostFragment

これは遷移の元となる画面のレイアウトに追加する特別なウィジェットです。
画面遷移は、このFragment内のレイアウトを入れ替えて実現します。

## NavController

NavControllerは、NavigationGraph内の現在の位置を追跡するためのオブジェクトです。
NavigationGraph内を移動する際に、NavHostFragment内のコンテンツを遷移先のアイテムに交換します。

以上がNavigationコンポーネントで実際に使用するレイアウト、ウィジェット、オブジェクトです。

# Navigationの追加

まずは、プロジェクトにNavigationを追加します。

## Navigation Editorの有効化

 プロジェクトにNavigationを追加する前に、NavigationではNavigationEditorを使用して画面遷移を管理するため、設定からNavigationEditorを有効に設定しておきます。

 Android Studioのメニューから以下のように選択します。

`  File > Android Studio > Preferences `
![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543318167727_+2018-11-27+20.28.45.png)

  その後、左の一覧から***Experimental***カテゴリを選択し、Editorの**Enable Navigation Editor**を選択したあとに、Android Studioを再起動します。
![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543318170874_+2018-11-27+20.29.01.png)

## プロジェクトへのNavigationの追加

プロジェクトへNavigationを追加するためには、build.gradleのdependenciesに、Navigationコンポーネントを追加します。
今回は、Navigation内でFragmentを使用するため、以下の２つを追加します。
また、Kotlinを使用しているため、KTXを使用しています。

```groovy
dependencies {
  implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
  implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
}
```

追加後にgradleを同期します。
同期が完了すればNavigationを使用するための事前準備は完了です。

# Navigation Graphの作成

次にNavigationGraphを追加していきます。
NavigationGraphは、Navigationを扱うためのファイルで、NavigationEditorを使用して、画面遷移、渡す値などを設定することができます。
実態としては、他のレイアウトファイルと同様で、xmlファイルです。

プロジェクトのresフォルダで右クリックし、開いたメニューから以下のように選択して、リソースファイルの作成ダイアログの表示させます。
`  New > Android Resource File`
![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543318977250_+2018-11-27+20.42.39.png)

リソースファイル作成ダイアログの**File name**フィールドにファイル名を入力します。
今回の説明では、NavigationGraphのファイル名を「nav_graph」という名前にします。
名前は自由につけられるので、今後の手順で「nav_graph」が出てきた際には、自分でつけたファイル名に読み替えてください。

次に、**Resouce type**の項目から**Navigation**を選択します。
![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543319532922_+2018-11-27+20.51.01.png)

その他のフィールドは、特に変更する必要はないので、変更がしたい場合は、各自で変更してください。

**OK**を選択することでNavigation Graphファイルがnavigationフォルダ下に作成されます。
また、navigationフォルダは、NavigationGraphと同時に自動で作成されます。
![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543319542613_+2018-11-27+20.51.20.png)

# NavHostFragmentの追加

次に、起動時に表示される画面のレイアウトにNavHostFragmentを追加します。

NavHostFragmentは、Navigationを使用するために必要な特別なFragmentで、NavigationのデフォルトのNavHostです。
Navigation内での画面遷移は、このFragmentを切り替えながら実現していきます。
基本的には起動時に使用するActivityのレイアウトに以下のようなfragmentを追加します。(ConstraintLayoutを使用しています)

```xml
    <fragment
        android:id="@+id/nav_host_fragment"
        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"

        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />
```

重要なのは、以下の部分です。

```xml
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />
```

これらパラメータを指定することで、このfragmentをNavHostFragmentとして扱うことができます。
app:navGraphに使用するNavigationGraphを渡すことで、このFragment内でNavigationによる画面遷移を行うことができるようになります。

# Navigation Editorについて

Navigation Graphを開くと、Navigation Editorが開きます。
Navigation Editorは、NavigationGraphを扱う専用のエディタで、基本的にはこのエディタを使用してNavigationのそれぞれの設定を行っていきます。

通常のレイアウトのエディタと同じく、NavigationGraphにも**Design**タブと、**Text**タブが用意されています。
それぞれを切り替えることで、NavigationEditorとxmlファイルを表示することができます。

Navigation Editorの画面構成は単純で、XcodeのStoryBoardとも似ています。
エディタは、以下のような画面構成をしています。

**①** : 追加した画面が一覧で表示される部分です。
**②** : 視覚的に表示されたNavigation graphです。この部分を操作して、画面遷移などを実装していきます。
**③** : ②で選択した画面に関連付けられた属性が表示されます。画面への詳細な設定は個々で行います。

![](https://d2mxuefqeaa7sj.cloudfront.net/s_F260BF1078571694FD8E3799E8FA3E786BDCE4BAB1808BEF055EC27E40B4F156_1543320745769_+2018-11-27+21.08.49.png)

## Navigation Editorへの画面の追加方法

NavigationEditorを使用して画面を追加するには、画面上部の以下のボタンから追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/37264b74-2a33-0744-3ff9-aadf857c1eb4.png)

このボタンを選択すると、以下のような画面一覧が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/1726066c-493d-ad8f-e433-8f882c8168d2.png)

この一覧から一つ選択すると、NavigationEditorに指定した画面を追加することができます。
すでに生成されているFragment以外を使用したい場合は、**Create new destination**を選択することで新しいFragnemtを生成するためのダイアログが表示されます。

NavigationEditorに追加した画面同士を操作することで、画面遷移や値渡し、遷移時のアニメーションの設定を行うことができます。

### 起動時に表示されるFragment

アプリ起動時に表示されるFragmentには、家のアイコン ![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/447b87eb-fd43-c790-ba3b-c3bb0e3dcaea.png) が表示されています。
開始位置の設定は、NavigationGraphの**app:startDestination**属性で指定されています。

## 画面遷移の追加方法

Navigationでは、画面遷移をNavigationEditor上で、視覚的に管理することができます。
その追加方法を説明します。

### actionの生成

Navigationでは、画面遷移をactionとして扱います。
actionを生成するためには、Navigation Editor内で画面と画面同士を接続します。
遷移元となる画面を選択すると、枠の右側が○になっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/81de4366-f771-583f-933e-4ad872c428d1.png)

この○を引っ張って、遷移先の画面にドラッグします。
するとNavigationEditor上の画面同士が矢印で繋がれます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/8161263f-5517-4b8a-6e0a-d81a7ac0ae70.png)

この矢印が、**action**として定義されており、自動的にIDが振られます。
IDはデフォルトで、**action_遷移元Fragment名\_to\_遷移先Fragment名**となっています。

Textタブを選択し、xmlファイルを確認すると、遷移元のFragment内に以下のようなactionが定義されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/ae0ec140-0b61-79b5-6e28-12ff9a3eac45.png)

actionを追加した直後に自動生成されるコードには、IDと遷移先の情報(destination)のみが定義されています。
その他、actionには、画面遷移時のアニメーションなどの情報を追加していきます。(画面遷移時のアニメーションの設定は後述します。)

### actionの使用

actionは、定義しただけでは何も行いません。
Fragmentなどの実装の中で、NavigationControllerにactionを渡すことで、画面遷移の処理を実行させることができます。

NavigationControllerを呼び出すためには、
KTXに含まれている以下の拡張関数の**navigate()**を使用すると楽です。
引数には、実行したいactionのIDを渡します。

```kotlin
Fragment.findNavController()
View.findNavController()
Activity.findNavController()
```

例えば、ボタンを押された際に画面遷移するようにする場合は、ButtonのClickListener内に以下のようなコードを追加します。

```kotlin
button.setOnClickListener {
    findNavControlelr().navigate(R.id.action_id)
}
```

これで、ボタンがタップされた際に、画面遷移の処理が実行されます。

## 画面遷移時の値渡し

Navigationでの画面遷移時でも、もちろん値渡しを行うことが可能です。
画面遷移時を値を渡したい場合は、まずNavigationGraphを操作します。
値が渡される方のFragmentを選択すると、Attributesに**Arguments**という項目が表示されます。
ここに、渡したい値の設定を追加していきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/b45e5803-d7be-2c80-d8db-76e639f79750.png)

まず、Argumentsの＋ボタンをタップします。
すると、以下のようなダイアログが表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/c2ff4625-1b68-763c-9c88-30a056c4dca0.png)

ここにそれぞれ設定したい値を入力・選択していきます。

* Name: パラメーター名
* Type: パラメーターの型
* Nullable: Nullを許容するか
* Default Value: デフォルト値

今回は、"inputText"というパラメーター名のString型で、nullを許容しない、デフォルトが空文字の値を渡せるように設定したいと思います。

設定は以下のようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/4036d48d-9a56-043e-11b4-17c94802afaa.png)

設定を行ったら**Add**を選択することで、Argumentを追加できます。

実際に値を渡すには、遷移元の処理で、画面遷移を行う際に、navigate()にパラメーターとして追加する必要があります。
Bundleで渡す方法が一番簡単なのですが、パラメーター名や型が明瞭ではないので、今回はSafeArgsを使用します。

### SafeArgsの追加

まずは、SafeArgsをプロジェクトに追加します。

gradleファイルに以下の内容を追加します。

```groovy
buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.0.0-rc02"
    }
}
```

今回は、Kotlinしか使っていないのでこちらを追加します。

```groovy
apply plugin: "androidx.navigation.safeargs.kotlin"
```

Javaのみもしくは、JavaとKotlinを含んでいる場合は、こちらを追加します。

```groovy
apply plugin: "androidx.navigation.safeargs"
```

これで、SafeArgsをプロジェクトに追加できます。

### 値渡し処理の実装

追加したSafeArgsを使用し、値を渡す処理を実装します。

以下ように実装します。

```kotlin
// 入力された文字列の取得
val inputText = secondFragmentEditText.text.toString()
val action = SecondFragmentDirections
    .actionSecondFragmentToArgumentFragment(inputText)

findNavController().navigate(action)
```

SecoundFragmentDirectionsというのは、自動的に生成されるクラスです。
今回は、遷移元がSecondFragmentなので、そこに"Directions"をあわせただけの名前になっています。

このクラスには、actionの情報が含まれているため、actionにつけられているIDの、"action_secondFragment_to_argumentFragment"をキャメルケースにしたメソッドが作られています。
このメソッドは、引数に遷移先のArgmentsの情報を持っています。
そのため、遷移先のArgmentsのパラメーター名および型を確認しながら渡す値を設定することができます。

### 受け取り側の実装

渡された値を実装します。

```kotlin
val args: ArgumentFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    argumentFragmentTextView.text = args.inputText
}
```

ArgumentFragmentArgsというクラスは、こちらも自動生成されるクラスです。
ArgumentFragmentと"Args"をあわせた名前になっています。

**navArgs()**というのは、ktxに含まれているもので、Argmentsの取得を簡単に行ってくれています。
ArgumentFragmentには、Argmentsに設定した"inputText"が含まれているので、値が渡されていれば取得することができます。
上記の例では、取得したinputTextの値をTextViewに設定しています。

ktxを使用していない場合は、ArgumentFragmentArgsを自分で生成する必要があるのですが、取得できるargumentがnullableなため、処理がめんどくさくなってしまうので、なるべくktxを使用すると良いと思います。

ktxを利用しない場合のArgumentFragmentArgsの取得方法

```kotlin
val args = ArgumentFragmentArgs.fromBundle(arguments!!)
argumentFragmentTextView.text = args.inputText
```


## 画面遷移のアニメーションの設定
Navigationでは、設定を行わなければ、遷移時にアニメーションはありません。
予め用意されている遷移のアニメーションもあるのですが、自分でアニメーションを作成し、使用することも可能です。

### レイアウトからのアニメーション設定

遷移時のアニメーションを動的に設定する必要がない場合は、レイアウトから設定が可能です。

Actionを選択すると、Attributesに**Animations**という項目が表示され、
Enter, Exit, Pop Enter, Pop Exitという4項目を設定することができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/275913/2e17a6ab-2e59-a049-f24f-a0474e167643.png)

設定可能な項目のそれぞれの役割は以下のようになっています。

* 新しい画面に遷移する際のアニメーション
    * Enter
        * 遷移処理発生時に、**遷移先**の画面が実行するアニメーション
    * Exit
        * 遷移処理発生時に、**遷移元**の画面が実行するアニメーション

* 画面がスタックから削除される際のアニメーション
    * Pop Enter
        * 前の画面に戻る際に、**戻った際に表示される**画面が実行するアニメーション
    * Pop Exit
        * 前の画面に戻る際に、**現在表示されている**画面が実行するアニメーション

この項目にそれぞれのアニメーションファイルを設定していくことで、遷移時にアニメーションが実行されます。
デフォルトで用意されているアニメーションを以下のように設定することで、新しい画面に遷移する際は、画面が右側から出てくるように見え、戻る際は画面が右側に戻っていくように見えるアニメーションを設定することができます。

<img width="222" alt="スクリーンショット 2019-03-13 10.34.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/275913/ac03a20e-689b-4972-b7ca-c0352721fa0c.png">

遷移時のアニメーション設定は、actionに紐付けられるため、xmlファイルを確認すると、以下のように項目が追加されています。

```xml
<action
    android:id="@+id/action_firstFragment_to_thirdFragment"
    app:destination="@id/thirdFragment"
    app:enterAnim="@anim/slide_in_right"
    app:exitAnim="@anim/slide_out_left"
    app:popEnterAnim="@anim/slide_in_left"
    app:popExitAnim="@anim/slide_out_right"/>
```

### コードからのアニメーション設定

遷移時のアニメーションを動的に変更したい場合は、コードからアニメーションを追加することも可能です。

コードから追加する場合は、遷移処理を行う際に、NavOptionを生成し渡すことで、アニメーションを設定することができます。

```kotlin
val navOption = navOptions {
                anim {
                    enter = R.anim.nav_default_enter_anim
                    exit = R.anim.nav_default_exit_anim
                    popEnter = R.anim.nav_default_pop_enter_anim
                    popExit = R.anim.nav_default_pop_exit_anim
                }
            }
findNavController().navigate(
    R.id.action_firstFragment_to_fourthFragment,
    null,
    navOption)
```

設定に関しては、レイアウトから追加する場合と同様で、enter, exit, popEnter, popExitにそれぞれの動作時に実行したいアニメーションを設定していくだけです。


# まとめ

画面遷移の情報が視覚的に確認できるので、Intentでの遷移よりも、ミスが減り、どこの画面から遷移してきているかも確認しやすいと思います。
型安全で値を渡せることや、遷移時のアニメーションを設定しやすいことも大きなメリットだと思います。

Intentでの画面遷移方法からはやり方が大きく変わるので、なかなかNavigationに手が出ないという人もいるかと思いますが、記事を読んでいただければ分かる通り、難しい部分は全く無いので、使って見るのも良いかと思います。
あとは、使っていきながら使いやすいところ使いにくいところを見つけて行ければと思っています！！

今回の記事をまとめるために作成したNavigationのサンプルは、GitHubにおいてあります。
https://github.com/m-kikuchi777/navigation_sample