Navigationの実装方法まとめ

kotlinにおける`Fragment`間の遷移を制御する`Navigation`の使い方、実装方法についてまとめていきます。

# この記事の内容
公式の[ドキュメント](https://developer.android.com/guide/navigation?hl=ja)やトレーニング「[Android Kotlin の基礎](https://developer.android.com/courses/kotlin-android-fundamentals/overview?hl=ja)」のレッスン3「ナビゲーション」を参考に実装のポイントとエッセンスをまとめていきます。
そもそも`Navigation`とは何か、という点については触れません。

### 前提知識

* kotlinの基礎的な文法
* AndroidStudioの使い方／アプリの作り方
* 画面や画面部品の配置方法

### 開発環境

* Windows 10 Home
* Android Studio 4.2.1

# 作成するサンプル
3つの画面を持ち、ボタンクリックにより画面遷移を行うごく簡単なアプリを作成します。
まったく意味のないアプリですが、画面間の遷移は`Navigation`でコントロールすることを目標とします。

# GetStarted
それでは作業に取り掛かりましょう。
まずは`Fragment`の作成から。

## build.gradel(app)の更新
事前準備として`DataBinding`を有効にしておきます。
_**build.gradle(app)**_ ファイルを開き、`plugin`、`android`、`dependencies`各ブロックに以下の通り追記してください。

```gradle
plugins {
    // (略)
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs'
}

android {
    // (略)
    buildFeatures {
        dataBinding true
    }
}

dependencies {
    // (略)
    implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
    implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
}

```

## build.gradle(Project)の更新
今度はプロジェクトレベルの _**build.gradle**_ ファイルを開き、`ext`、`dependencies`各ブロックに以下の通り追記します。

```gradle
ext {
    // (略)
    navigationVersion = "2.3.0"
}
dependencies {
    // (略)
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
```

## Fragmentの作成
AndroidStudioでEmptyプロジェクトを新規作成したら _**layout**_ フォルダを右クリックし、 _**New→Fragment→Fragment(Blank)**_ を選択します。
名前（Fragment Name）は何でも良いのですがここでは _**StarFragment**_ としておきます。Fragment Layout Nameは入力内容に応じ自動で設定されます（この場合は _**fragment_start**_ となります）。
レイアウトファイルと合わせて _**StartFragment.kt**_ ファイルが _**java**_ フォルダ内に自動で生成されます。
作成した _**fragment_start**_ には適当に`Button`を作成しておきます。

同様に _**SecondFragment**_  、_**FinalFragment**_ も作成しておきしょう。

## レイアウトファイルの設定
作成したレイアウトファイルを開きます。親のビューにカーソルを当て、Windowsなら`Alt+Enter`、Macなら`Option+Enter`を押します。
`Convert to data binding layout`をクリックすると自動でレイアウトファイルが`DataBinding`用のものに変換されます。
<img width="500" alt="01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/41094f80-86e9-61fa-39a4-893495ebde42.png">

この設定をしておかないと次のタスクで`Fragment`の`Binding`オブジェクトが作成できないので注意してください。


## Fragment.ktファイルの設定
作成した _**fragment_start.xml**_ に対応する _**StartFragment.kt**_ ファイルを開きます。
なんか自動生成されたコードが色々とうざったいので`class`ブロックの中身全部消しちゃってください。
`Fragment.kt`ファイルでは`onCreateView`をoverrideする必要があるので実装します。
下記の通り`Binding`オブジェクトを生成し、その`root`である`View`を返すコードを実装してください。

```kotlin
class StartFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val binding = DataBindingUtil.inflate<FragmentStartBinding>(
            inflater,
            R.layout.fragment_start,
            container,
            false
        )
        return binding.root
    }
}
```

同じ要領で _**SecondFragment**_  、_**FinalFragment**_ も編集してあげましょう。

## activity_main.xmlの編集
プロジェクト作成時に自動生成される _**activity_main.xml**_ が空っぽのままなので _**StartFragment**_ と紐づけてあげます。
`android:name`には自身のパッケージから設定してあげてください。

```xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <fragment
            android:id="@+id/startFragment"
            android:name="com.warpstudio.android.navigationsample.StartFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    </LinearLayout>
</layout>
```
この段階で試しにアプリを起動してみましょう。
__*StartFragment*__ の画面が表示されるはずです。
が、この段階ではボタンをクリックしても何も起こりません（何も実装していないからね）。
次の章では画面遷移を実装していきます。

# Navigationの定義
前章で作成した画面間の遷移を制御する`Navigation`を定義していきます。

## NavigationGraphの作成
_**res**_ フォルダを右クリックし、 _**New→Android Resource File**_ を選択します。
ファイル名は _**navigation**_ 、Resource typeは _**Navigation**_ をそれぞれ設定してください。 _**res**_ フォルダ直下に _**navigation**_ フォルダが作成され、その中に _**navigation.xml**_ というファイルがあるはずです。

## activity_main.xmlの（再）編集
編集したばかりの _**activity_main.xml**_ ですが、早くも更新をします。
先ほどは`Fragment`との紐づけを行いましたが、ここでは`NavHostManager`と紐づけます。
`android:name`、`app:navGraph`、`app:defaultNavHost`を下記の通り設定します。

```xml
        <fragment
            android:id="@+id/startNavHostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/navigation"
            app:defaultNavHost="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
```

## Fragmentの配置
`NavigationGraph`に作成済の`Fragment`を配置していきます。 _**navigation.xml**_ をデザインビューで開きます。
`New Destination`ボタンをクリックし、_**StartFragment**_ から _**FinalFragment**_ まで3つの`Fragment`を配置します。
なおプレビューが`Preview Unavailable`と表示され、見れない場合は _**navigation.xml**_ をコードビューで開き、各`Fragment`の`Tool`属性を下記の通り設定してあげます。

```xml
    <fragment
        android:id="@+id/startFragment"
        android:name="com.warpstudio.android.navigationsample.StartFragment"
        android:label="StartFragment"
        tools:layout="@layout/fragment_start"/>
```

## Destinationの設定
配置した`Fragment`間の遷移を定義します。デザインビューで`Fragment`をクリックすると右側から矢印が伸ばせるので遷移対象の`Fragment`に結びます。
この操作により自動でナビゲーションの`id`と`Destination`が設定されます。画面遷移にはこの`id`を利用します。
こんな感じになりました。
<img width="500" alt="02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/72cf9e5e-8f59-f9ba-b66a-c7a47b234458.png">

## ClickHandlerの実装
_**StartFragment**_ を開き`ClickHandler`を実装します。

```kotlin
        binding.startButton.setOnClickListener {view: View ->
            view.findNavController().navigate(R.id.action_startFragment_to_secondFragment)
        }
```

同様に _**SecondFragment**_  、_**FinalFragment**_ のボタンにも`Destination`に従いリスナを登録します。
これによりボタンのクリックにより画面遷移が行われるようになりました。
アプリを起動して確認してみてください。

# Fragment間で引数を渡す
前章までが`Fragment`の基本的な実装方法となりますが、`Fragment`間で引数を扱いたいケースもあると思います。
その実装方法として推奨されているのが`SafeArgs`です。

## ClickHandlerの改修
前章の最後で実装した`ClickHandler`を下記の通り改修します。

```kotlin
        binding.startButton.setOnClickListener { view : View ->
            view.findNavController().navigate(StartFragmentDirections.actionStartFragmentToSecondFragment())
        }

```

従来は`id`から呼び出していた`action`を`NavDirection`クラスから呼び出す方式に変更しています。
同様に残りの`Fragment`も改修しましょう。

## 引数の設定
_**navigation.xml**_ で定義した`Fragment`間の`Path`に引数の情報を加えます。
引数を受け取りたい`Fragment`を選択した状態でデザインビュー右側のAttributeペインの _**Arguments**_ を開いてください。
**+**ボタンをクリックすると下記のダイアログが表示されるので引数として設定する変数の名前と型を登録します。
<img width="300" alt="03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073313/aff7a571-12e2-18c2-5b35-c9d38671a2ef.png">

異なる型の引数を複数設定することもできますよ。

## ClickHandlerの改修（その2）
またもや`ClickHandler`のメンテナンスです。
上記の状態でコンパイルしようとしてもエラーが出るのですが、それは遷移元の`Fragment`が`navigate`する際に必要な引数を与えていないからですね。
今回は _**StartFragment**_ から _**SecondFragment**_ の遷移にあたり`Integer`と`String`の引数を設定しました。
なので`ClickHandler`は下記の通りとなります。

```kotlin
        val args1: Int = 1
        val args2: String = "hoge"
        binding.startButton.setOnClickListener { view : View ->
            view.findNavController()
                .navigate(StartFragmentDirections
                    .actionStartFragmentToSecondFragment(args1, args2))
        }
```

## 遷移先のFragmentの処理
遷移先の`Fragment`では以下の方法で引数を受け取ることができます。

```kotlin
        val args = SecondFragmentArgs.fromBundle(requireArguments())
        val args1 = args.argsInt
        val args2 = args.argsStr
```

# Recap
今回の記事は以上となります。
読んでいただいた方は長丁場ありがとうございました。
ご覧の通り、`Fragment`や`Navigation`の概念的な内容について触れてはおりませんので、深く学習したい方は公式のドキュメントをぜひ参照してください。
また実際には戻るボタンの制御やここに`ViewModel`が加わったらどうなるのか等、実務レベルでは不十分な点もあるかと思います。
ただコーディングの際、「あれ、ここどうやって書くんだっけ」ってなったときなど、クイックリファレンス的な使い方でお役に立てれば嬉しいですね。

ではまた。