Edited at

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


Navigationとは?

公式ページ

画面遷移に関する事をいい感じに実装できる優れもの

以下の3つのコンポーネントを理解する事が重要 :sparkles:


  • Navigation graph


    • 画面遷移を1つのXMLファイルで集中的に管理



  • NavHost


    • ナビゲーショングラフから目的地を表示する空のコンテナ



  • NavController


    • ナビゲーション管理




Navigationを使う事の利点


  • Fragmentトランザクションの管理

  • Up, Backイベント処理

  • アニメーションとトランジションのための標準化されたリソースを提供

  • DeepLinkの処理

  • 最小限の追加作業で、ナビゲーションパネルや下部ナビゲーションなどのナビゲーションUIパターンを含めることができる

  • Safe Args

  • ViewModelのサポート


Navigation Editor

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


:computer:環境構築


新しく NavigationSample としてプロジェクトを作成し

dependenciesはNavigationとSafeArgsをインストールします。

rootのbuild.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

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"
}

公式ドキュメント


:pencil: 実装



Navigation Graph の作成

AndroidStudioでリソース作成時にリソースタイプを Navigation に設定し、

ファイル名を入力して作成

ファイル名はGet started通りに nav_graph.xml で作成

nav1.png


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


  • MainFragment

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

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... は以下の様にも書ける

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


Navigation Editor での設定

「New Destination」をクリックすると既に存在しているFragmentやActivityが

追加できる。またこの画面から新規にFragment等を作成もできる。

nav2.png

そして上記の2つのフラグメントを繋ぐように設定した navigation graphがこちら

<?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 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

ドキュメント

Navigation Graphにargumentを追加

    <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を渡す側

        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を受け取る側

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

XXXXFragmentDirectionsXXXXXFragmentArgs が自動生成される :sparkles:


DialogFragment

ダイアログのNavigationを行う方法です。

先に表示先のダイアログを作成します。

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

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

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

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

            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

googleissue

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


  • currentDestinationを確認する

if (it.findNavController().currentDestination?.id == R.id.fragment_dashboard) {

it.findNavController().navigate(R.id.fragment_detail)
}


  • エラーを握りつぶす

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