Navigation コンポーネントが提供する Kotlin ベースのドメイン固有言語(DSL)は、Kotlin のタイプセーフ ビルダーを利用しています。この API を使用すると、XML リソース内ではなく、Kotlin コード内で宣言的にグラフを作成できるため、アプリのナビゲーションを動的に作成したい場合に便利です。たとえば、アプリで外部ウェブサービスからナビゲーション設定をダウンロードしてキャッシュし、その設定を使用してアクティビティの onCreate()
関数内でナビゲーション グラフを動的に作成できます。
依存関係
Kotlin DSL を使用するには、アプリの build.gradle
ファイルに次の依存関係を追加します。
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
グラフを作成する
まずは、Sunflower アプリに基づく基本的な例を見てみましょう。この例では、home
と plant_detail
の 2 つのデスティネーションがあります。home
デスティネーションは、ユーザーがアプリを初めて起動したときに表示され、ユーザーの庭にある植物のリストを示します。ユーザーが植物の 1 つを選択すると、アプリは plant_detail
デスティネーションに移動します。
図 1 に、これらのディスティネーションと、アプリが home
から plant_detail
に移動する際に使用するアクション to_plant_detail
と、plant_detail
デスティネーションで必要となる引数を示します。
Kotlin DSL ナビゲーション グラフをホストする
アプリのナビゲーション グラフを作成する前に、グラフをホストする場所が必要です。この例ではフラグメントを使用して、グラフを FragmentContainerView
内の NavHostFragment
にホストします。
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
この例では app:navGraph
属性が設定されていないことに注意してください。グラフは res/navigation
フォルダでリソースとして定義されていないため、アクティビティの onCreate()
プロセスの一部として設定する必要があります。
XML では、アクションはデスティネーション ID を 1 つ以上の引数と関連付けます。ただし、Navigation DSL を使用する場合はルートに引数を含められます。つまり、DSL を使用する場合はアクションという概念はありません。
次のステップでは、グラフを定義するときに使用する定数を定義します。
グラフの定数を作成する
XML ベースのナビゲーション グラフは、Android ビルドプロセスの一環として解析されます。数値定数はグラフで定義された id
属性ごとに作成されます。このビルド時に生成される静的 ID は、実行時にナビゲーション グラフをビルドする場合は利用できないため、Navigation DSL は ID の代わりにルート文字列を使用します。各ルートは一意の文字列で表されます。スペルミスによるバグのリスクを軽減するために、定数として定義することをおすすめします。
引数を処理するときに、これらはルート文字列に組み込まれます。このロジックをルートに組み込むことにより、スペルミスによるバグの発生リスクを軽減できます。
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
NavGraphBuilder DSL でグラフを作成する
定数を定義したら、ナビゲーション グラフを作成できます。
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
この例では、fragment()
DSL ビルダー関数を使用し、後置ラムダで 2 つのフラグメントのデスティネーションを定義しています。この関数には定数から取得するデスティネーションのルート文字列が必要です。デスティネーションのラベルなど、設定を追加する場合はオプションのラムダに加え、引数とディープリンクの埋め込みビルダー関数も受け取ります。
各デスティネーションの UI を管理する Fragment
クラスは、パラメータ化された型として山かっこ(<>
)で囲まれて渡されます。これは XML を使用して定義されたフラグメント デスティネーションで android:name
属性を設定するのと同じ役割を果たします。
Kotlin DSL グラフで移動する
最後に、標準の NavController.nav() 呼び出しを使用して home
から plant_detail
に移動します。
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
次の例のとおり、PlantDetailFragment
で引数の値を取得できます。
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
ナビゲーション時に引数を指定する方法については、デスティネーション引数を指定するをご覧ください。
このガイドの残りの部分では、一般的なナビゲーション グラフの要素、デスティネーション、グラフの作成時にそれらを使用する方法について説明します。
デスティネーション
Kotlin DSL には、Fragment
、Activity
、NavGraph
という 3 つのデスティネーション タイプのサポートが組み込まれています。各デスティネーションには、デスティネーションの作成と構成に使用できる独自のインライン拡張関数があります。
Fragment デスティネーション
fragment()
DSL 関数は、実装するフラグメント クラスにパラメータ化できます。この関数は、このデスティネーションに割り当てる一意のルート文字列に続いて、ラムダを受け取ります。ここでは Kotlin DSL グラフで移動するに記載のとおり、追加の設定ができます。
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
Activity デスティネーション
activity()
DSL 関数は、このデスティネーションに割り当てる一意のルート文字列を受け取りますが、実装したアクティビティ クラスにパラメータ化されません。代わりに、後置ラムダでオプションの activityClass
を設定できます。この柔軟性によって、明示的なアクティビティ クラスでは意味を成さない場合に、暗黙的インテントを使用して起動しなければならないアクティビティの Activitiy デスティネーションを定義できます。Fragment デスティネーションと同様に、ラベル、引数、ディープリンクを設定することもできます。
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
Navigation graph デスティネーション
navigation()
DSL 関数を使用して、ネストされたナビゲーション グラフをビルドできます。この関数は、グラフに割り当てるルート、グラフの開始デスティネーションのルート、グラフの詳細を構成するラムダという、3 つの引数を受け取ります。有効な要素には、他のデスティネーション、引数、ディープリンク、デスティネーションの説明ラベルなどがあります。このラベルは NavigationUI を使用してナビゲーション グラフを UI コンポーネントにバインドする場合に役立ちます。
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
カスタム デスティネーションのサポート
Kotlin DSL を直接サポートしていない新しいデスティネーション タイプを使用している場合、これらのデスティネーションを Kotlin DSL に追加するには、addDestination()
を使用します。
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
別の方法としては、単項プラス演算子を使用して、新しく作成されたデスティネーションを直接グラフに追加することもできます。
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
デスティネーション引数を指定する
デスティネーションでは、オプションまたは必須の引数を指定できます。アクションはすべてのデスティネーション ビルダータイプの基本クラスである NavDestinationBuilder
の argument()
関数を使用して指定できます。この関数は NavArgument
の作成と構成に使用される、文字列およびラムダとして引数の名前を受け取ります。
ラムダ内では、引数のデータ型、デフォルト値(該当する場合)、null 可能とするかどうかを指定できます。
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
defaultValue
を指定すると、型が推測されます。defaultValue
と type
の両方を指定する場合は、型が一致する必要があります。使用可能な引数の全リストについては、NavType のリファレンス ドキュメントをご覧ください。
カスタム型を指定する
ParcelableType
や SerializableType
などの特定の型は、ルートまたはディープリンクに使用される文字列からの値の解析をサポートしていません。これらの型は実行時にリフレクションに依存しないためです。カスタム NavType
クラスを指定することで、ルートまたはディープリンクからの型の解析方法を完全に制御できます。これにより、Kotlin Serialization ライブラリまたはその他のライブラリを使用して、カスタム型のリフレクションのないエンコードとデコードが可能になります。
たとえば、検索画面に渡される検索パラメータを表すデータクラスは、Serializable
(エンコードとデコードをサポート)と Parcelize
(Bundle
からの保存と復元をサポート)の両方を実装できます。
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
カスタム NavType
は次のように記述できます。
val SearchParametersType = object : NavType<SearchParameters>(
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: SearchParameters) {
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): SearchParameters {
return bundle.getParcelable(key) as SearchParameters
}
override fun serializeAsValue(value: SearchParameters): String {
// Serialized values must always be Uri encoded
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): SearchParameters {
// Navigation takes care of decoding the string
// before passing it to parseValue()
return Json.decodeFromString<SearchParameters>(value)
}
}
その後、その他の型と同様に Kotlin DSL で使用できます。
fragment<SearchFragment>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
NavType
は各フィールドの書き込みと読み取りの両方をカプセル化します。つまり、デスティネーションに移動するときに形式が一致するように NavType
も使用する必要があります。
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
このパラメータはデスティネーションの引数から取得できます。
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
ディープリンク
ディープリンクは XML によるナビゲーション グラフと同様に、どのデスティネーションにも追加できます。Kotlin DSL を使用して明示的ディープリンクを作成するプロセスには、デスティネーションのディープリンクを作成するで定義されている手順が適用されます。
ただし、暗黙的ディープリンクを作成する場合は、分析して <deepLink>
要素に含める XML ナビゲーション リソースが存在しないため、AndroidManifest.xml
ファイルに <nav-graph>
要素を追加する方法は利用できず、代わりに手動でアクティビティにインテント フィルタを追加する必要があります。指定するインテント フィルタは、アプリのディープリンクのベース URL パターン、アクション、MIME タイプと一致する必要があります。
個別にディープリンクされたデスティネーションごとに、より具体的な deeplink
を deepLink()
DSL 関数を使用して指定できます。この関数は URI パターンを表す String
、インテント アクションを表す String
、mimeType を表す String
を含む NavDeepLink
を受け取ります。
次に例を示します。
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
追加できるディープリンクの数に制限はありません。deepLink()
を呼び出すたびに、そのデスティネーションで保持されているリストに新しいディープリンクが追加されます。
パスベースとクエリベースのパラメータも定義して、より複雑な暗黙的ディープリンクを指定する場合は以下のとおりです。
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
文字列補間を使用して定義を簡素化できます。
制限事項
Safe Args プラグインは、Directions
クラスと Arguments
クラスを生成する XML リソース ファイルを参照するため、Kotlin DSL とは互換性がありません。
さらに詳しく
Kotlin DSL と Navigation Compose のコードで型安全性を実現する方法については、Navigation の型安全性のページをご覧ください。