Navigationを実際に使ってみた時のまとめ

## Navigationとは?

[公式ページ](https://developer.android.com/guide/navigation/?hl=JA)

画面遷移に関する事をいい感じに実装できる優れもの
以下の3つのコンポーネントを理解する事が重要 :sparkles: 

- Navigation graph
    - 画面遷移を1つのXMLファイルで集中的に管理
- NavHost
    - ナビゲーショングラフから目的地を表示する空のコンテナ
- NavController
    - ナビゲーション管理

### Navigationを使う事の利点

- Fragmentトランザクションの管理
- Up, Backイベント処理
- アニメーションとトランジションのための標準化されたリソースを提供
- DeepLinkの処理
- 最小限の追加作業で、ナビゲーションパネルや下部ナビゲーションなどのナビゲーションUIパターンを含めることができる
- Safe Args 
- ViewModelのサポート

### [Navigation Editor](https://developer.android.com/guide/navigation/navigation-getting-started?hl=JA)

AndroidStudio 3.3以上の環境でナビゲーションをGUIで操作できる

###  :computer:環境構築
****
新しく `NavigationSample` としてプロジェクトを作成し
dependenciesはNavigationとSafeArgsをインストールします。

rootの`build.gradle`

```gradle
    repositories {
        google()
    }
    dependencies {
        // SafeArgs
        def nav_version = "2.1.0-alpha06"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
```

moduleの `build.gradle`

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

dependencies {
    // Navigation
    def navi_version = "2.1.0-alpha06"
    implementation "androidx.navigation:navigation-fragment-ktx:$navi_version"
    implementation "androidx.navigation:navigation-ui-ktx:$navi_version"
}
```

[公式ドキュメント](https://developer.android.com/jetpack/androidx/releases/navigation?hl=JA#declaring_dependencies)

### :pencil: 実装
****

#### Navigation Graph の作成

AndroidStudioでリソース作成時にリソースタイプを `Navigation` に設定し、
ファイル名を入力して作成
ファイル名はGet started通りに `nav_graph.xml` で作成

![nav1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/8f32923d-cfe2-dfe1-bb7f-d33be403a32d.png)


#### 2つのフラグメントの作成

- MainFragment

```kotlin
class MainFragment : Fragment() {

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

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        buttonToSecond.setOnClickListener {
            Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_secondFragment)
        }
    }
}
```

- SecondFragment

```kotlin
class SecondFragment : Fragment() {

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

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        buttonToMain.setOnClickListener {
            Navigation.findNavController(it).navigate(R.id.action_secondFragment_to_mainFragment)
        }
    }
}
```
※ Navigation.findNavController... は以下の様にも書ける

```kotlin
buttonToSub.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_xxx, null))
```

#### Navigation Editor での設定

「New Destination」をクリックすると既に存在しているFragmentやActivityが
追加できる。またこの画面から新規にFragment等を作成もできる。

![nav2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/8df1afdb-5d99-cb63-2823-1ef1f27eb93e.png)

そして上記の2つのフラグメントを繋ぐように設定した navigation 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/mainFragment">

    <fragment android:id="@+id/mainFragment" android:name="slowhand.com.navigationsample.MainFragment"
              android:label="fragment_main" tools:layout="@layout/fragment_main">
        <action android:id="@+id/action_mainFragment_to_secondFragment" app:destination="@id/secondFragment"/>
    </fragment>
    <fragment android:id="@+id/secondFragment" android:name="slowhand.com.navigationsample.SecondFragment"
              android:label="fragment_second" tools:layout="@layout/fragment_second">
        <action android:id="@+id/action_secondFragment_to_mainFragment" app:destination="@id/mainFragment"/>
    </fragment>
</navigation>
```
このようになりました。

#### NavHostFragmentの指定

MainActivityのレイアウトファイルで、NavHostFragmentを追加しています。

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        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:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph"/>

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

`app:defaultNavHost="true"` 端末の戻るボタンを制御する。
またデフォルトにできるのは通常1つのNavHostのみになります。

### 別Activityへの遷移

NavigationEditorのNew DestinationでActivityも追加できるので、
Activityを追加し、Fragmentからactionを追加してやる :sparkles: 

----
### Safe args

[ドキュメント](https://developer.android.com/guide/navigation/navigation-pass-data?hl=JA#Safe-args)

Navigation Graphにargumentを追加

```xml
    <fragment android:id="@+id/secondFragment" android:name="slowhand.com.navigationsample.SecondFragment"
              android:label="fragment_second" tools:layout="@layout/fragment_second">
        <action android:id="@+id/action_secondFragment_to_mainFragment" app:destination="@id/mainFragment"/>
        <!-- こちら -->
        <argument android:name="text"
                  android:defaultValue="default"
                  app:argType="string" />
    </fragment>
```

argumentを渡す側

```kotlin
        buttonToSecond.setOnClickListener {
//            ↓の様にも書ける
//            val bundle = bundleOf("text" to "hello")
//            Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_secondFragment, bundle)

            Navigation.findNavController(it).navigate(
                MainFragmentDirections.actionMainFragmentToSecondFragment(text = "hello"))
        }
```

argumentを受け取る側

```kotlin
textView.text = SecondFragmentArgs.fromBundle(arguments ?: return).text
```

`XXXXFragmentDirections` や `XXXXXFragmentArgs` が自動生成される :sparkles: 

### DialogFragment

ダイアログのNavigationを行う方法です。
先に表示先のダイアログを作成します。

```kotlin
class SimpleDialogFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        return AlertDialog.Builder(requireActivity())
            .setTitle("")
            .setMessage("")
            .setPositiveButton("OK") { _, which ->
                ...
            }
            .create()
    }
}
```

Navigation Editorの「New Destination」から作成したDialogFragmentを追加します。
次にDialogに渡すパラメータを追加して行きます。

![nav3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/57b451bb-2e56-22f8-41a2-8812054269b8.png)

一回ビルドしなおすとパラメータをSafeArgsで受け取れるようになります。

```kotlin
val args = arguments?.let { SimpleDialogFragmentArgs.fromBundle(it) }
```

実際に使う場合は遷移元のFragmentをMainFragmentとすると

```kotlin
            Navigation.findNavController(it).navigate(
                MainFragmentDirections.actionMainFragmentToSimpleDialogFragment(arg = 0))
```
こんな感じで書けます :sparkles:

###  :bomb: バッドノウハウ
****

```
java.lang.IllegalArgumentException: navigation destination xxxxx.dev:id/action_xxxFragment_to_xxxDialogFragment is unknown to this NavController
```
こんなエラーが出た場合はDestinationの繋ぎ方を見直して間違ってないか要確認。

----

```
java.lang.IllegalStateException: View 
xxxx does not have a NavController set
```

DialogからDialog、またはFragmenntへ遷移しようとしていた為。
どうもDialogから他のFragmentへは遷移できなさそうでした。
[stackoverflow](https://stackoverflow.com/questions/56647476/why-dialog-does-not-have-a-navcontroller-missing)
[googleissue](https://issuetracker.google.com/issues/134089818)

繋ぎ方を見直しても間違ってなさそうな場合、以下の対応どちらかを検討する

- currentDestinationを確認する

```kotlin
if (it.findNavController().currentDestination?.id == R.id.fragment_dashboard) {
    it.findNavController().navigate(R.id.fragment_detail)
}
```

- エラーを握りつぶす

```kotlin
try { 
    it.findNavController().navigate(R.id.toDetails)
} catch (e: IllegalArgumentException) {
    // User tried tapping 2 links at once!
    Timber.e("Can't open 2 links at once!")
}
```


### :link: 参考URL
****

- [Android Jetpack Component ~Navigation 編~ - Qiita](https://qiita.com/ru_ri21/items/2fdcef6f522f61f1545e)
- [Navigation + DialogFragment - Kenji Abe - Medium](https://medium.com/@star_zero/navigation-dialogfragment-a3747ff504cd)
- [Resolving crash "IllegalArgumentException x is unknown to this NavController" | Jake Lee](https://blog.jakelee.co.uk/resolving-crash-illegalargumentexception-x-is-unknown-to-this-navcontroller/)