Android Jetpack の Navigationを使ってみた

Androidで画面をたくさんある場合、その画面の数分layout.xmlとFragmentを作成することになるが、どのFragmentからどのFramgmentへ、何の条件のときに遷移するかを制御するのがコードでベタベタになってしまいます。

Android Jetpack の Navigationを使うとその辺がスッキリします。
# 参考URL
[本家Android developers](https://developer.android.com/guide/navigation?hl=ja)
[[Android] 10分で作る、Navigationによる画面遷移](https://qiita.com/tktktks10/items/7df56b4795d907a4cd31)
[Navigation Componentの使い方（概要〜画面遷移〜データの受け渡し編）](https://qiita.com/naoi/items/8384561d30111c8704b3)

簡単に手順だけ説明しますので詳細はgitHubに上がっているソースを見てください。
[gitHub](https://github.com/cozyk100/AndroidStudioProjectsPublic/tree/main/NavigationKotlin)

# build.gladle
build.gradleに依存関係を追加します
```
dependencies {
    ・・・
    def nav_version = "2.5.2"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
    implementation "androidx.navigation:navigation-compose:$nav_version"
    ・・・
}
```
# resの下にnavigationフォルダ、xmlを作る
resの下に「navigation」という名前でフォルダを作ります。
![Screenshot_20221121_220857.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/7bd6085d-456e-9e04-6faf-1a837adb67ed.png)
フォルダを作ったら、「navigation」を右クリック、→「新規」→「Navigation Resource File」を選択します。**これは「res」を右クリックしても出てきません。**
![Screenshot_20221121_221036.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/06b64791-392e-8420-dc65-7a1717c34888.png)
こんな画面が出てきます。ファイル名にxmlのファイル名を入れます。(何でもいい)とりあえず、「navigation_graph.xml」と入れます。
![Screenshot_20221121_221358.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/e727527d-6fa3-89a3-964f-205baa36496b.png)
とりあえず、navigation_graph.xmlをダブルクリックして開いてみましょう。こんな画面が表示されるはずです。
![Screenshot_20221121_224244.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/66b40953-69ec-0d4a-4ebe-6f40eaf9b77d.png)
今はまだ空っぽでなにもありません。
# Fragmentを作成、MainActivityとつながるようにする
取り敢えず、Fratmentを3つ作り、MainActivityからつながるようにします。ここはAcdroidStudioの雛形に沿って作ります。
| class          | layout xml       |
|:--------------:|:----------------:|
| FragmentMain.kt| fragment_main.xml|
| Fragment01.kt  | fragment_01.xml  |
| Fragment02.kt  | fragment_01.xml  |

FragmentMainはMainActivityから呼ばれる最初のFragmentです。そのあとは
FragmentMain、Fragment01.kt、Fragment02.ktの3つのFragmentを3各点回しで画面遷移できるようにします。
# まず、MainActivityから
MainActivityは単純に、activity_main.xmlのレイアウトを呼んであげます。
```kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).apply {
            setContentView(this.root)
        }
    }
}
```
次に、activity_main.xml
```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/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/navigation_graph"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```
activity_main.xmlのミソは、この3行です。
```xml
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/navigation_graph"
app:defaultNavHost="true"
```
# FragmentのNavigationを編集する
Navigationのフローの編集に入ります。開いたnavigation_graph.xmlの上の＋のボタンをクリックするとFragmantの一覧が出てきます。
![Screenshot_20221122_211242.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/2f1b3b54-e27f-f05a-b469-af53cad5d727.png)
FragmentMain、Fragment01、Fragment02を適当な位置に配置します。こんな感じです。
![Screenshot_20221122_211815.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/5a886153-e4df-47d5-d241-7f8c25c0ed83.png)
お互いのFragmentを線で結んでやります。お互い3つのFragmentが行き来できるようにすると、このようなフローになります。
![Screenshot_20221122_212035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/2159895a-aaa7-7807-846a-c0a969b4c2be.png)
それぞれのFragmentから2本づつ、合計6本の線がでているのがわかるでしょうか。
![Screenshot_20221122_212324.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/c2cc84b6-1337-40f7-007d-7c834069865a.png)
上から順に
Mian → 01
Main → 02
01 → 02
01 → Main
02 → 01
02 → Main
の6本です。
# FragmentMain
FragmentMainはFragment01とFragment02に遷移するフローがありますので、これをボタンのイベントにくくりつけてやります。
FragmentMain.kt
```kotlin
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.button01.setOnClickListener {
            // Fragment01へ
            Navigation.findNavController(binding.root).navigate(R.id.action_fragmentMain_to_fragment01) 
        }
        binding.button02.setOnClickListener {
            // Fragment02へ
            Navigation.findNavController(binding.root).navigate(R.id.action_fragmentMain_to_fragment02) 
        }
    }
```
ここで注目してほしいのはFragmentMainの中で直接遷移先のFragmentを指定しているわけではないということです。
Mian → 01 の場合、
R.id.action_fragmentMain_to_fragment01
が遷移先を示しています。このR.id.XXXXはNavigationフローの編集のウィンドウのConponetTreeに出ているactionの名前と一致します。つまり、フローでつながっている1本の線を指しているわけです。R.id.action_XXXX_to_YYYYのXXXとYYYYがモロ、Fragmentの名前なのでアレですが、ここは名前を変えることができるので、意味のあるイベント名とかにもできます。例えばXXX_BUTTON_PUSHとか、LOGINとか。
# 同じ要領で、Fragment01.kt
Fragment01.ktも同じ要領で、01 → 02、01 → Mainに遷移する処理を書いてやります
```kotlin
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = Fragment01Binding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.button02.setOnClickListener {
            // Fragment02へ
            findNavController().navigate(R.id.action_fragment01_to_fragment02)
        }
        binding.buttonMain.setOnClickListener {
            // FragmentMainへ
            findNavController().navigate(R.id.action_fragment01_to_fragmentMain)
        }
    }
```
# 同じ要領で、Fragment02.ktも
Fragment02.ktも同じ要領で、02 → 01、02 → Mainに遷移する処理を書いてやります
```kotlin
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = Fragment02Binding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.button01.setOnClickListener {
            // Fragment01へ
            findNavController().navigate(R.id.action_fragment02_to_fragment01)
        }
        binding.buttonMain.setOnClickListener {
            // FragmentMainへ
            findNavController().navigate(R.id.action_fragment02_to_fragmentMain)
        }
    }
```
これで3つのFragmentの3各点回しができるようになりました。＼(^o^)／
# actionに引数を定義してみる
ただ、これだけでは物足りません。各FragmentとFragmentを繋ぐ線のactionに引数を定義することができます。
フローのFragmentを選択するとその右のAttributeのビューに引数を追加できるビューが表示されます。
![Screenshot_20221122_215743.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/9b80fe1c-9c17-cf13-d626-92ec2c3f97b6.png)
試しにStringの引数を2つ定義してみました。
param1
param2
この引数の型ですが、String、Intぐらいであればうまく動くようですが、他の型だとうまく動かないという報告もあります。
Main → 01
に行くaction(FragmentとFragmentを繋ぐ線)がどうなっているか見てみます。
![Screenshot_20221122_220210.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/49073d8b-0433-9cb6-aafb-1b09bd6467c4.png)
「Argment Default Vale」に追加した引数、param1、param2が表示され、ここでデフォルト値が設定できます。デフォルト値は各action(FragmentとFragmentを繋ぐ線)毎に別々に設定できます。
同じように、Main → 02
に行くaction(FragmentとFragmentを繋ぐ線)は、
![Screenshot_20221122_220508.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/2f446875-da02-d139-1474-780f781a410e.png)
同じように引数、param1、param2が表示され、デフォルト値が設定できるようになっています。

同じ要領で、Fragment01、Fragment02も引数を増やし、6本のaction全部に2個づつ引数を増やして、デフォルト値を設定してやります。

Fragmentの方は、引数をFragmentのEditTextから取ってきて設定してやる処理と、前のFragmentから引数が渡ってくるのでTextVoewに表示してやる処理を追加します。
FragmentMain.kt
```kotlin
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.message1.setText(param1)
        binding.message2.setText(param2)
        binding.button01.setOnClickListener {
            val action = FragmentMainDirections.actionFragmentMainToFragment01()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            // Fragment01へ
            Navigation.findNavController(binding.root).navigate(action)
        }
        binding.button02.setOnClickListener {
            // Fragment02へ
            val action = FragmentMainDirections.actionFragmentMainToFragment02()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            Navigation.findNavController(binding.root).navigate(action)
        }
    }
```
Fragment01.kt
```kotlin
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.message1.setText(param1)
        binding.message2.setText(param2)
        binding.button02.setOnClickListener {
            val action =Fragment01Directions.actionFragment01ToFragment02()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            // Fragment02へ
            findNavController().navigate(action)
        }
        binding.buttonMain.setOnClickListener {
            val action =Fragment01Directions.actionFragment01ToFragmentMain()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            // FragmentMainへ
            findNavController().navigate(action)
        }
    }
```
Fragment02.kt
```kotlin
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.message1.setText(param1)
        binding.message2.setText(param2)
        binding.button01.setOnClickListener {
            val action = Fragment02Directions.actionFragment02ToFragment01()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            // Fragment01へ
            findNavController().navigate(action)
        }
        binding.buttonMain.setOnClickListener {
            val action = Fragment02Directions.actionFragment02ToFragmentMain()
            action.param1 = binding.message1.text.toString()
            action.param2 = binding.message2.text.toString()
            // FragmentMainへ
            findNavController().navigate(action)
        }
    }
```

以上、駆け足でAndroidのNavigationの説明しましたが、完成形は[gitHub](https://github.com/cozyk100/AndroidStudioProjectsPublic/tree/main/NavigationKotlin)を見てください。