この記事はand factory Advent Calendar 2020の5日目の記事です。
昨日は@ichikawa7ssさんの【超ミニマム】AWS AppSync + AmplifyでiOSチャットアプリを作るでした。
はじめに
Navigation ComponentでKotlin DSLを使用できるようになってたので簡単に解説します。
本記事は下記ドキュメントの抜粋なので詳細はこちらをご参照ください。
https://developer.android.com/guide/navigation/navigation-kotlin-dsl?hl=ja
※サンプルコードとしてhttps://github.com/android/sunflower を用いています。
XMLを使用したNavigation
fragmentやaction, argumentなどナビゲーション関連の情報 (NavGragph) を管理するのに通常は下記のようなXMLリソースを使います。
<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"
app:startDestination="@id/view_pager_fragment">
<fragment
android:id="@+id/view_pager_fragment"
android:name="com.google.samples.apps.sunflower.HomeViewPagerFragment"
tools:layout="@layout/fragment_view_pager">
<action
android:id="@+id/action_view_pager_fragment_to_plant_detail_fragment"
app:destination="@id/plant_detail_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/plant_detail_fragment"
android:name="com.google.samples.apps.sunflower.PlantDetailFragment"
android:label="@string/plant_details_title"
tools:layout="@layout/fragment_plant_detail">
<action
android:id="@+id/action_plant_detail_fragment_to_gallery_fragment"
app:destination="@id/gallery_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
<argument
android:name="plantId"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/gallery_fragment"
android:name="com.google.samples.apps.sunflower.GalleryFragment"
android:label="@string/plant_details_title"
tools:layout="@layout/fragment_gallery">
<argument
android:name="plantName"
app:argType="string" />
</fragment>
</navigation>
作成したグラフは、NavHostFragmentのapp:navGraph
として指定することでNavControllerを使った移動が行えるようになります。
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<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"
app:navGraph="@navigation/nav_garden" />
</layout>
Kotlin DSLを使用したNavigation
Kotlin DSLを使うとKotlinのコード内で宣言的にNavigationのグラフを作成できるようになります。
以下の依存関係を追加します。
dependencies {
def nav_version = "2.3.1"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
}
ホストの作成
ホストとして用いるFragmentはXMLの場合と同様にNavHostFragmentです。
ただし、NavGraphはKotlinのコードで作成するので削除します。
<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"
- app:navGraph="@navigation/nav_garden" />
+ app:defaultNavHost="true" />
グラフの定数の作成
XMLでグラフを作成する場合android:id
を定義すればR.id
を介して一意なidにアクセスできましたが、Kotlin DSLでグラフを作成する場合、独自の定数を定義してidを管理する必要があります。
ドキュメントではobjectを用いて定数を定義する方法を挙げています。
object nav_graph {
const val id = 1 // graph id
object dest {
const val home = 2
const val plant_detail = 3
}
object action {
const val to_plant_detail = 4
}
object args {
const val plant_id = "plantId"
}
}
グラフの作成
ActivityのonCreate内でグラフを作成します。
createGraph()
はNavGraphを返すメソッドで、第三引数のラムダ (NavGraphBuilder.() -> Unit
)内でDestinationを追加していきます。
下記のコードはfragment()
DSLでFragment Destinationを作成しています。
fragment()
メソッドのbuilder内でactionやargumentなどの定義を行うことで画面遷移や引数を追加することができます。
class GardenActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_garden)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host) as NavHostFragment
navHostFragment.navController.apply {
graph = createGraph(nav_graph.id, nav_graph.dest.home) { // this: NavGraphBuilder
// Fragment Destinationを作成
fragment<HomeViewPagerFragment>(nav_graph.dest.home) {
label = getString(R.string.home_title)
action(nav_graph.action.to_plant_detail) {
destinationId = nav_graph.dest.plant_detail
}
}
fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
label = getString(R.string.plant_detail_title)
argument(nav_graph.args.plant_id) {
type = NavType.StringType
}
}
}
}
}
}
目的地への移動
NavController.navigate()
メソッドを用いて移動します。
idはnav_graphで定義しておいたものを使います。
private fun navigateToPlant(plantId: String) {
val args = bundleOf(nav_graph.args.plant_id to plantId)
findNavController().navigate(nav_graph.action.to_plant_detail, args)
}
Deep Link
deepLink()
メソッドを用いるだけで作成することができます。
strings.xmlファイル内にディープリンクを定義し、getString()
メソッドを使って書くことも可能です。
fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
label = getString(R.string.plant_details_title)
argument(nav_graph.args.plant_id) {
type = NavType.StringType
}
deepLink("https://www.example.com/plants/{plantId}/")
// deepLink(getString(R.string.deep_link_plants)) もOK
}
ただしここで作成されるのは明示的ディープリンクで、暗黙的ディープリンクを作成したい場合はAndroidManifestにインテントフィルタを追加しなければなりません。
XMLを使ってNavGraphを作成している場合は、AndroidManifestファイルの<nav-graph>
にそのグラフを指定してあげればよかったのですが、Kotlin DSLではその方法を使えないのですべて自分で定義する必要があります。
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.example.com"
android:pathPattern="/plants.*"
android:scheme="https" />
</intent-filter>
その他
- この他にもCustom DestinationやNavOptionsの指定などもKotlin DSLで実装可能
- SafeArgsは互換性なし