Navigationとは?
画面遷移に関する事をいい感じに実装できる優れもの
以下の3つのコンポーネントを理解する事が重要
- Navigation graph
- 画面遷移を1つのXMLファイルで集中的に管理
- NavHost
- ナビゲーショングラフから目的地を表示する空のコンテナ
- NavController
- ナビゲーション管理
Navigationを使う事の利点
- Fragmentトランザクションの管理
- Up, Backイベント処理
- アニメーションとトランジションのための標準化されたリソースを提供
- DeepLinkの処理
- 最小限の追加作業で、ナビゲーションパネルや下部ナビゲーションなどのナビゲーションUIパターンを含めることができる
- Safe Args
- ViewModelのサポート
Navigation Editor
AndroidStudio 3.3以上の環境でナビゲーションをGUIで操作できる
環境構築
新しく 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"
}
実装
Navigation Graph の作成
AndroidStudioでリソース作成時にリソースタイプを Navigation
に設定し、
ファイル名を入力して作成
ファイル名はGet started通りに nav_graph.xml
で作成
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等を作成もできる。
そして上記の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を追加してやる
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
XXXXFragmentDirections
や XXXXXFragmentArgs
が自動生成される
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に渡すパラメータを追加して行きます。
一回ビルドしなおすとパラメータをSafeArgsで受け取れるようになります。
val args = arguments?.let { SimpleDialogFragmentArgs.fromBundle(it) }
実際に使う場合は遷移元のFragmentをMainFragmentとすると
Navigation.findNavController(it).navigate(
MainFragmentDirections.actionMainFragmentToSimpleDialogFragment(arg = 0))
こんな感じで書けます
バッドノウハウ
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!")
}