Navigation Componentで新規登録画面の実装方法（graphのネストと戻るボタンの実装）

# はじめに
 **Navigation Component** を使うと新規登録画面の画面遷移が簡単に実装できると耳にしました。
[公式サイト](https://developer.android.com/guide/navigation)にも実装方法が記載されていますが、なかなか難しかったので記事としてまとめます。

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

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

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

* Navigation Component UIで新規登録の画面遷移を実装できる

# 動作イメージ
こんな動きをする新規登録の画面遷移を **Navigation Component** で実装したい！！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/3f33e8cd-f3d4-a258-c781-f98c32e48f4d.gif" width="40%" />


# 機能概要
よくある話ですが、アプリをインストールして **新規登録** をタップして、ウィザード形式でユーザ登録などを行います。
登録が完了した画面や登録完了後に戻ってきたログイン画面で戻るボタンなどをタップすると正しい画面遷移ができています。
下図のようなイメージです。

![01_実現したい機能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/1e7e6ccc-e180-72f1-9daf-f8a8a8d100a5.png)


これってどのように実装しているのだろうと不思議に思うこともあります。
Navigation Componentに、[公式サイト](https://developer.android.com/guide/navigation)に実現の助けになるような機能が搭載されています。
この記事では実現方法を説明していきます。

# Fragmentの動作
まず、実装方法の前にFragmentの画面やオブジェクトがどのように管理されているか理解して置く必要があります。
Navigation Componentでは、Fragmentの画面はActivityと同じ用にスタックで管理されています。
下図様なイメージです。

![02fragmentのスタック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/29039d7f-7ba9-0115-7769-dff523b77fe5.png)


そのため、完了画面に遷移したあと、戻るボタンをタップするとデフォルトの動きでは確認画面に遷移していまいます。
これがFragmentの基本的な動作となります。
そのため、完了画面から戻るボタンや戻る機能を追加するときに、Login画面までスタックをクリアする必要があります。
上記の内容を理解した上で、実装方法を確認してください。


# 実装方法
## 前提知識
実装方法の前提知識としては、[この記事](https://casareal-ls.qiita.com/naoi/items/14374707d2ce604aeacd)を理解しておいてください。

## アクション

画面遷移でスタックをクリアしたい画面にアクションを接続することにより実現可能です。
下図の様なイメージで、完了画面からログイン画面にアクションを接続します。

![03_navigationファイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/abfea964-5e21-c0f3-b5af-daac5c0db624.png)


## graphのネスト

デスティネーションが増えていくと、navigationファイルの編集画面が操作しづらくなってしまいます。
なので、機能毎などでgraphをネストするとわかりやすくなります。

１機能のデスティネーションをすべて選択した状態で、「 **group into nest graph** 」ボタンをクリックします。
操作方法は下図を参照してください。

![04_navigationのネスト方法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/b0d65e65-6450-5e85-40fc-00be6e53c9b6.png)


「 **group into nest graph** 」ボタンをクリックすると、ネストしたgraphは **角がまるい四角** になります。
ネストしたgraphの詳細を確認したい場合は、 **角がまるい四角** をダブルクリックするだけで下図が表示されます。

![05_ネストした中身の確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/801118c4-4990-546d-be7d-5e313a84c4ad.png)


ネストしたgraphからもとの画面に戻りたい時は、Component Treeで外側をクリックすると元の画面に戻ります。

![06_ネストの外を表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/c27b69d3-8372-221f-f388-3c45ae53020d.png)


実際のコードを確認すると、navigationタグがネストしていることが確認できます。

![07_コードで確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/3ca7518f-39f7-a753-7c74-5d76332681f7.png)


ネストを利用することにして、機能ごとにnavigaitonをまとめられるので便利ですね。
navigationファイルを新規作成してincludeしても同じ事が行なえます。

## スタックを指定したFragmentまでクリアする
先に説明した通り、ウィザード形式のように元の画面までスタックをクリアするには以下の手順を行います。

1. アクションに設定を追加する

アクションタグの属性に以下の２つを設定します。

* popUpTo属性
* popUpToInclusive属性

最終的に下記のようなアクションになります。

```xml
<action
    android:id="@+id/action_completeToRegisterUserFragment_to_loginFragment"
    app:destination="@id/loginFragment"
    app:popUpTo="@id/loginFragment"
    app:popUpToInclusive="true" />
```

### popUpTo属性

[公式サイト](https://developer.android.com/guide/navigation/navigation-navigate#pop)に説明があります。

>Navigation ライブラリに対し、navigate() 呼び出しの一環として、バックスタックからいくつかのデスティネーションをポップするように指示します。属性値として、スタック上に残す最も新しいデスティネーションの ID を指定します。

スタックにある、navigationは一番新しいデスティネーションを探し出してくれるみたいですね。
しかし、これだけではうまく動作しません。

### popUpToInclusive属性
こちらも[公式サイト](https://developer.android.com/guide/navigation/navigation-navigate#pop)に説明があります。

> app:popUpToInclusive="true" を追加すると、app:popUpTo 内で指定したデスティネーションもバックスタックから削除するように指示できます。

これで古いLogin画面を削除してアクションので指定しているデスティネーションを新規にスタックに乗っけているわけですね。

なるほど、その他の説明もかいてありますが、興味のある方は[公式サイト](https://developer.android.com/guide/navigation/navigation-navigate#pop)を参照してください。


## 戻るボタンの処理を変更する
Androidで用意されているソフトウェアやハードウェアの戻るボタンの挙動も修正しなければなりません。
[公式サイト](https://developer.android.com/guide/navigation/navigation-custom-back)に詳しく書かれていますが、どうやら ** OnBackPressedDispatcher#addCallback** で戻るボタンの挙動を変えるみたいです。

実際のコードは以下の通りです。

```kotlin
// 戻るボタンを押下されても、ログイン画面に戻るようにこの画面のライフサイクル中の戻るボタンのコールバックを変更
requireActivity().onBackPressedDispatcher.addCallback(this) {
    val navController = findNavController()
    val action =
        CompleteToRegisterUserFragmentDirections.actionCompleteToRegisterUserFragmentToLoginFragment()
    navController.navigate(action)
}
```

 **addCallback()** メソッドの引数に **ライフサイクルオーナーを渡す方法** と **渡さない方法** があります。 

違いは以下のとおりです。

|方法|挙動|
|:----|:----|
|ライフサイクルオーナーを渡す方法|そのライフサイクルの間、Callbackで指定した戻るボタンの挙動が有効になる|
|ライフサイクルオーナーを渡さない方法|アプリが終了するまで、Callbackで指定した戻るボタンの挙動が有効にになる|

今回のケースでは、ライフサイクルオーナーを渡す方法が正しいとなります。


以上で実装は完了です。

# まとめ
Androidアプリでウィザード形式を実装する方法がとても簡単になりました。
Navigation Componentを今まで避けて通ってきてきた私でしたが、今後は積極的に利用したくなりました。