Android(Kotlin)Navigation はじめの一歩

2020/8/27

はじめに

アプリバーを実装したくて色々調べていたら、 Navigation コンポーネントの存在を知りました。 ドキュメントを見る限りは便利そうだったので、導入検討のため、最低限の画面遷移処理を実装してみました。

実装内容

Gradle

Gradle プラグインを追加するので、 モジュールレベルビルドファイルだけでなく、 トップレベルビルドファイルも修正します。

build.gradle

Safe Args Gradle プラグインを追加します。

また、モジュールレベルビルドファイルとバージョンを合わせたいので、バージョン情報も記述しておきます。

@@ -1,11 +1,13 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 buildscript {
     ext.kotlin_version = "1.4.0"
+    ext.nav_version = "2.3.0"
     repositories {
         google()
         jcenter()
     }
     dependencies {
+        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
         classpath "com.android.tools.build:gradle:4.0.1"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

app/build.gradle

Kotlin 向け Safe Args プラグインを適用します。プラグイン適用を記述する箇所ですが、どうやら apply plugin: 記述は順番も重要らしく、一番最初に記述したりすると、safeargs plugin must be used with android plugin とエラーが出てしまうのでご注意ください。

また、Kotlin 向け依存関係も追加します。

@@ -1,6 +1,7 @@
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
+apply plugin: 'androidx.navigation.safeargs.kotlin'
 
 android {
     compileSdkVersion 29
@@ -29,6 +30,8 @@ dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation 'androidx.core:core-ktx:1.3.1'
     implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
+    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'androidx.test.ext:junit:1.1.1'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

ナビゲーショングラフ

app/src/main/res/layout/activity_main.xml

Navigation コンポーネントのデフォルト NavHost 実装である NavHostFragment を Activity に追加します。この箇所で画面(正確にはデスティネーション)が切り替わることになります。

@@ -7,4 +7,11 @@
     android:orientation="vertical"
     tools:context=".MainActivity">
 
+    <fragment
+        android:id="@+id/fragment"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:navGraph="@navigation/nav_graph" />
 </LinearLayout>

app/src/main/res/navigation/nav_graph.xml

Android Studio において GUI ベースで作成します。作業フローの中で追加デスティネーションとしてフラグメントを生成することもできますし、最初にフラグメントを作ってしまって、デスティネーションとして追加することもできます。

ここでは後述する二つのフラグメントをお互いに遷移できるように設定しました。

@@ -0,0 +1,26 @@
+<?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/firstFragment">
+
+    <fragment
+        android:id="@+id/firstFragment"
+        android:name="com.example.myapplication.FirstFragment"
+        android:label="fragment_first"
+        tools:layout="@layout/fragment_first" >
+        <action
+            android:id="@+id/action_firstFragment_to_secondFragment"
+            app:destination="@id/secondFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/secondFragment"
+        android:name="com.example.myapplication.SecondFragment"
+        android:label="fragment_second"
+        tools:layout="@layout/fragment_second" >
+        <action
+            android:id="@+id/action_secondFragment_to_firstFragment"
+            app:destination="@id/firstFragment" />
+    </fragment>
+</navigation>

デスティネーション用フラグメント

遷移を確認できるように、TextViewButton を持つだけのデスティネーション用フラグメントを二つ作成します。

遷移時に引数として必要な actionSafe Args のおかげで簡単に記述することができます。

app/src/main/java/com/example/myapplication/FirstFragment.kt

@@ -0,0 +1,25 @@
+package com.example.myapplication
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.navigation.findNavController
+
+class FirstFragment : Fragment() {
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_first, container, false).apply {
+            findViewById<Button>(R.id.first_button).apply {
+                setOnClickListener {
+                    val action = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
+                    it.findNavController().navigate(action)
+                }
+            }
+        }
+    }
+}

app/src/main/java/com/example/myapplication/SecondFragment.kt

@@ -0,0 +1,25 @@
+package com.example.myapplication
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import androidx.navigation.findNavController
+
+class SecondFragment : Fragment() {
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_second, container, false).apply {
+            findViewById<Button>(R.id.second_button).apply {
+                setOnClickListener {
+                    val action = SecondFragmentDirections.actionSecondFragmentToFirstFragment()
+                    it.findNavController().navigate(action)
+                }
+            }
+        }
+    }
+}

app/src/main/res/layout/fragment_first.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".FirstFragment">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="first_fragment" />
+
+    <Button
+        android:id="@+id/first_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+</LinearLayout>

app/src/main/res/layout/fragment_second.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".SecondFragment">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="second_fragment" />
+
+    <Button
+        android:id="@+id/second_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+
+</LinearLayout>