はじめに
Jetpackで追加された、 Navigation Component を調べたいな〜とずっと考えていましたが、ようやく使い方が理解できたので使い方をこの記事では説明します。
動作環境
この記事の動作環境は以下のとおりです。
Android Studio:4.1.1
Kotln:1.4.21
Open JDK:1.8
compileSdkVersion:30
targetSdkVersion:30
minSdkVersion:23
Navigation Component:2.3.2
目標
以下を目標とします。
- 基本的な利用方法を理解する
- 画面遷移方法を理解する
- 画面間でデータの受け渡しを行う方法を理解する
動作イメージ
この記事の最終動作イメージは下図です。
サンプルコード
早くサンプルコード見せろって方は下記からダウンロードしてください。
Navigation Componentの概要
まずはNavigation Componentって何???って話をしていきます。
Androidアプリ開発において、画面遷移は全体像を理解するために、各画面を作成してからOfficeなどを使い、画面遷移図を作成しているかと思います。
iOSアプリ開発ではStoryboardが存在し、IDE上で視覚的に画面遷移が理解出来ます。
Androidアプリ開発でも、Storyboardのように視覚的に直感で画面遷移が理解出来ます。
それだけではなく、メリットがあります。公式サイトではその他にも以下のメリットを上げていますね。
- フラグメント トランザクションを処理する。
- デフォルトで「上」アクションと「戻る」アクションを正しく処理する。
- アニメーションと遷移用の標準化されたリソースを提供する。
- ディープリンクを実装して処理する。
- 最小限の追加作業で、ナビゲーション ドロワーやボトム ナビゲーションなどのナビゲーション UI パターンを組み込める。
- Safe Args - 宛先間で移動とデータの受け渡しを行う際に型の安全性を提供する Gradle プラグイン。
- ViewModel のサポート - ViewModel のスコープをナビゲーション グラフに設定して、グラフの宛先間で UI 関連のデータを共有できます。
なかなか、いろんな事が簡単に実現できそうな感じです。
フラグメントのトランザクション管理をやってくれるのは本当にありがたいです。
ということは、Navigation Componentってのは、どうやらFragmentを使って実現しているようです。
フラグメントで最低限の知識がないかも不安って方はFragmentの基本を参照してください。
実装方法
必要なファイル群
それでは、実装方法について説明します。
実装するにも、何から手をつけてよいやらと思います。
Navigation Componentで必要となるActivityやFragmentを図にしてみました。
以下のファイル群が登場します。
- navigationの設定ファイル(XML)
- Activity
- Fragment
それぞれの役割をまとめるとこの様な感じです。
ファイル | 役割 |
---|---|
Activity | Fragmentのコンテナが基本的な役割だが、アクションバーやツールバー、ナビゲーションドロワーの制御を行います。 |
Fragment | 実際に表示する画面や機能を持つ |
navigationの設定ファイル(XML) | 画面遷移や画面間のデータの受け渡しの設定、画面遷移のアニメーションの設定などを管理する |
navigationの設定ファイルで画面遷移を表現すると言ったところです。
内部的にはActivityのコンテナ部分でFragmentを操作しているようです。
Fragmentを準備する
必要なファイル群を理解すると、navigationの設定ファイルを作る前にFragmentなどを事前に準備しておく必要があることが分かります。
まずはBlankでもいいので、Fragmentを3ほど用意してみます。
今回は以下の3つのFragmentを用意しました。そのぞれの画面の役割も説明します。
Fragment名 | 役割 |
---|---|
StartFragment | 名前が入力でき、FirstFragmentやSecondFragmentに遷移するボタンを保持する。入力した名前をFirstFragmentやSecondFragmentに受け渡す |
FirstFragment | StartFragmentから画面遷移してきて、入力した名前を受け取る。受け取った名前を表示する |
SecondFragment | FirstFragmentと同じくStartFragmentから画面遷移してきて、入力した名前を受け取る。受け取った名前の文字数を表示する |
これで必要なFragmentが準備できました。
navigationの設定ファイル作成
Fragmentが準備できたらnavigationの設定ファイルを作成します。
これはAndroid StudioのGUI操作でサクサク作れます。
手順は以下のとおりです。
- resを右クリック
- Resource Typeを Navigation を選択。
- name にファイル名を入力、今回は my_navi を入力
- 必要なライブラリーがbuild.gradleのdependenciesに存在しない場合、ダイアログが表示されるため、OKを押下
- res フォルダに navigation フォルダが作成され、my_navi.xmlファイルが出来ている事を確認する
実際の操作画面は下図です。
家マークが付いているFragmentは、初期画面を意味します。初期画面の設定は画面上部の家マークで変更可能です。
Fragmentをnavigation設定でFragmentの追加と接続
navigationの設定ファイルが作成したら、いよいよ、Fragmetの画面遷移をGUIで表していきます。
画面(Destination:デスティネーション)の追加
まずは、navigationの設定ファイルにFragmentを追加しましょう。
手順は以下のとおりです。
- new destinationボタンをクリック
- 対象のFragmentをクリック
Navigation Componentでは、Fragmentの事をDestination(デスティネーション)と呼ぶようです。
今回は以下の3つを追加します。
- StartFragment
- FirstFragment
- SecondFragment
実際の操作イメージは以下のとおりです。
追加が完了すると左下のComponent Treeに追加したデスティネーション(画面)が表示されます。
画面遷移の線(Action:アクション)を接続
デスティネーション(画面)が追加できたら、画面遷移の線で表します。
線のことを アクション と呼びます。
アクションの接続手順は以下のとおりです。
- 遷移元のデスティネーション(画面)の右端に表示する○から遷移先にドラッグアンドドロップします。
実際の操作イメージは下図の通りです。
接続が完了するとComponetTreeにもアクションが表示されます。
Activityの設定
navigationの設定ファイルがある程度完成したら、Activityのコンテナを設定して作成したNavigation Componentを設定していきます。
手順は以下のとおりです。
- Activityのレイアウトファイルを開く
- プロジェクト作成時に生成されたTextViewなどは削除しておく
- デザインタブが選択された状態にする
- Palette の検索で fragment と入力
- NavHostFragment を画面にドラッグアンドドロップする
- NavHostFragment の選択ダイアログが表示したら、作成したnavigation設定ファイルのmy_naviを選択する
実際の操作イメージは下図の通りです。
NavHostFragment を追加した後にCodeタブで確認をするとFragmentに警告が出ています。どうやら、 FragmentContainerView に変更すべし!!と出ているので修正します。
FragmentContainerViewについてはご自身で調べてください。
以下のように修正されていれば問題ありません。
ここまで設定が出来たら、動作確認を行ってみました。
StartFragment の画面が下図のように表示されれば問題ありません。
この時点で、画面遷移は起きません。
なぜなら、画面遷移の処理を記述する必要があるからです。
画面遷移
画面遷移するためには処理の記述が必要です。どの様な処理を記述するのかを公式サイトで調べてみると、以下の手順になるようです。
- NavHostを取得
- NavHostから以下のいずれかでNavControllerを取得
3. Fragment.findNavController()
4. View.findNavController()
5. Activity.findNavController(viewId: Int) - アクションを取得
- navigateでアクションを指定し画面遷移
ここでは、以下の2つが重要な役割を果たしているようです。
- NavHost
- NavController
それぞれの役割の概要は以下のとおりです。
クラス名 | 役割 |
---|---|
NavHost | Navigation用の単一コンテナ。直接操作せずに、NavControllerを通じて操作する。NavControllerを渡すメソッドを保持している。 |
NavController | NavHostを保持し、アプリケーションのNavigationを管理する。NavControllerが保持しているデスティネーションはナビゲーショングラフ(navigation設定ファイル)によって異なる |
どうやら、画面遷移にはNavControllerで行う事がわかります。
では、NavHostを取得しようと考えたら、Navigation Componentを利用するのであれば、 Safe Args を利用して、タイプセーフに移動することを推奨しているようです。
Safe Args を利用するとAndroid Studioの入力補完も効くので、オススメです。
Safe Args を利用するにはbuild.garadleに以下の修正を加える必要があります。
dependencies {
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
apply plugin: "androidx.navigation.safeargs.kotlin"
appのbuild.gradleですが、プロジェクトによっては以下のように修正する必要があります。
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
ここまで出来たら、ビルドしておきます。
公式サイトには、pluginを追加すると遷移元のデスティネーションからクラスが自動生成されるとのことですが、実際はされないのでビルドし直してAndroid Studioに生成してもらいます。
リビルドは下図の手順で行ってください。
準備が整ったので、画面遷移を実装していきます。
今一度、手順を振り返っておきます。
- NavHostを取得
- NavHostから以下のいずれかでNavControllerを取得
3. Fragment.findNavController()
4. View.findNavController()
5. Activity.findNavController(viewId: Int) - アクションを取得
- navigateでアクションを指定し画面遷移
NavHost を取得するとあります。しかし、ここまで、 NavHost なんてものを定義した記憶はありません。
実は、Activityのコンテナに設定しています。
Activityのレイアウトリソースファイルを見てみます。
<?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/host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/my_navi"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentContainerViewの android:name 属性を確認してみると、しっかり ** NavHostFragment** って記述がありますね。
どうやら、Activityにコンテナを設定したときにAndroid Studioが自動で設定してくれたみたいです。 app:navGraph 属性には、navigationの設定ファイルが指定されていますね。
ここで紐付いているようです。
なるほど、しかも公式サイトにはこんな記述があります。
FragmentContainerView を使用して NavHostFragment を作成する場合、または FragmentTransaction からアクティビティに NavHostFragment を手動で追加する場合、Navigation.findNavController(Activity, @IdRes int) からアクティビティの onCreate() で NavController を取得しようとするとエラーになります。NavHostFragment から直接 NavController を取得してください。
フラグメントを取得してそのままキャストすればいいとのことです。
なるほど、われわれはこうなるわけですね。
// NavHostの取得
val navHostFragment =
requireActivity().supportFragmentManager.findFragmentById(R.id.host_fragment) as NavHostFragment
次にNavControllerを取得します。NavControllerはNavHostから取得するので下記の記述になります。
// NavController取得
val navController = navHostFragment.navController
続いてActionを取得しましょう。
公式サイトに下記の記述があります。
Safe Args を有効にすると、このプラグインによって、定義した各アクションのクラスとメソッドを格納するコードが生成されます。
そうでしたね。自動でクラスを生成してくれるのでした。
アクションはその線の元に内部クラスとして生成されます。
今回は、StartFragmentから線を延ばしました。そのため、自動生成されたStartFragmentのクラスから取得します。
自動生成されるクラス名は以下のとおりです。
まずはFragmentのクラス名は以下のとおりです。
Fragmentのクラス名 + Directions
なので、StartFragmentは StartFragmentDirections という名前のクラスが生成されます。そしてそのクラスの内部クラスとしてActionのクラスが生成されます。
Actionのクラス名はnavigationの設定ファイルで指定したActionのid属性から生成されます。
変換ルールは「スネークケース→ローワーキャメルケース」
navigationの設定ファイルをcodeタブで確認すると下記のようになっています。
<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/my_navi"
app:startDestination="@id/startFragment">
<fragment
android:id="@+id/startFragment"
android:name="jp.co.casareal.navigationcomponent_sample.StartFragment"
android:label="fragment_start"
tools:layout="@layout/fragment_start" >
<action
android:id="@+id/action_startFragment_to_firstFragment"
app:destination="@id/firstFragment" />
<action
android:id="@+id/action_startFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
</navigation>
今回の場合は以下のように変換されます。
action_startFragment_to_firstFragment → ActionStartFragmentToFirstFragment
変換ルールを理解したところで、実際にActionを取得するコードを確認します。
Actionを取得するには、idをローワーキャメルケースのメソッドを呼び出します。
val action =
StartFragmentDirections.actionStartFragmentToFirstFragment()
Actionが取得できたら、いよいよ、最後の画面遷移です。
NavControllerのnavigateメソッドの引数にActionのオブジェクトを渡して呼び出すだけです。
navController.navigate(action)
この時点で動作確認をすると、入力したデータを遷移先の画面に受け渡しができてないですが、しっかり画面遷移できます。
画面間のデータの受け渡し
Navigation Componentで画面間のデータの受け渡しはいくつかやり方があります。
事前に Safe Args を有効 しているため、navigationの設定ファイルにデータを受け渡す設定をします。
データの受け渡しのイメージとしては、遷移先のFragmentに変数を設定し、Actionを取得する際にその変数に値を格納するイメージです。
設定ファイルはXMLなので、コードで編集も可能ですが、GUIでも設定が可能です。
手順は以下のとおりです。
- GUIでせっていする場合は、navigationの設定ファイルを開く
- デザインタブを選択する
- データを受け取る (遷移先の)Fragment を選択する
- Arguments の「+」ボタンをクリック
- 表示されたダイアログの nameを入力する
- type を選択する
実際の画面間イメージは下図の通りです。
Codeタブを開くと下図のように遷移先のFragmentに argument タグが定義されます。
<?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/my_navi"
app:startDestination="@id/startFragment">
<fragment
android:id="@+id/firstFragment"
android:name="jp.co.casareal.navigationcomponent_sample.FirstFragment"
android:label="fragment_first"
tools:layout="@layout/fragment_first" >
<argument
android:name="nameForConfirm"
app:argType="string"/>
</fragment>
</navigation>
受け渡しできるデータの方は下記の通りです。
詳細は公式サイトを確認してください。
型 | 記述例 |
---|---|
Integer | app:argType="integer" |
Float | app:argType="float" |
Long | app:argType="long" |
Boolean | app:argType="boolean" |
String app:argType="string" | |
参照 | app:argType="reference" |
Parcelable app:argType="" | |
Serializable | app:argType="" |
Enum | app:argType="" |
大体の型には対応しています。
遷移先にデータを受け渡すコードを記述していきます。
Actionを取得する際に、呼び出すメソッドに受け渡すデータを設定します。
val action =
StartFragmentDirections.actionStartFragmentToFirstFragment(nameForConfirm = strName)
最後にデータを受け取る処理です。
navigationの設定ファイルにargmentタグを追加すると、設定したFragmentのクラスから以下のルールでクラスが自動生成されます。
フラグメントのクラス名 + Args
FirstFragment であれば FirstFragmentArgs となります。
実際に取得するには navArgs 関数を呼び出します。
class FirstFragment : Fragment() {
// データが格納されているargsを取得
private val args:FirstFragmentArgs by navArgs()
}
args:FirstFragmentArgs が取得できればあとは簡単です。
FirstFragamentArgsのプロパティにnavigationの設定ファイルのargmentのname属性で指定した名前で追加されています。
実際のコードは下記のとおりです。
args.nameForConfirm
以上でデータの受け渡しが完了です。
まとめ
Navigation Componentの基本的な利用方法は多くあるため、覚えるのだけでも一苦労です。
しかし、他にも機能があるのでしっかり覚えておくことをおすすめします。