Navigationコンポーネントの使い方編【Android Jetpack】

Android

Android JetpackのNavigation(ナビゲーション)の使い方についてサンプルアプリを作りながら説明していきたいと思います。

スポンサーリンク

Navigationとは

Navigationとは、Android Jetpackに含まれるコンポーネントの1つです。

画面の遷移を簡単に定義することが可能で、 フラグメントのトランザクション管理や、戻るボタンの管理、画面遷移時にデータを安全に受け渡しなどをおこなってくれます。
また、Navigation Editorを使用することで画面遷移を視覚的に表現し、編集することが可能なところが魅力です。

開発環境

サンプルアプリ作成時の開発環境は以下です。

  • macOS Catalina 10.15.2
  • Android Studio 3.5
  • Kotlin 1.3.61

また、今回作成したサンプルアプリのソースコードは githubのNavigationSample に置いてあるので、気になる方は動かしてみてください。

作成するNavigationサンプルアプリ

今回作成するサンプルアプリでは、Navigationを使って5つのフラグメントを切り替えるアプリを作成します。

(手書きのため綺麗ではないですが)以下のような画面遷移を作ります。

  • 画面のNEXTボタンを押したら次の画面に進む
    • First -> Second
    • Second -> Third
    • Third -> Force
    • Force -> Fifth
  • 画面の左上の戻るボタンを押したら一つ前のフラグメントに戻る
    • Second -> First
    • Third -> Second
    • Force -> Third
  • Fifthで画面の左上の戻るボタンを押したらThirdに戻る
    • Fifth -> Third

上記のように、Fifthで戻るボタンを押した時のみ少し動作を変えたいと思います。
また、SecondからThirdの画面遷移と、ThirdからForceに画面遷移時には、SafeArgsでデータの受け渡しをおこなっていきます。

gradleに依存関係を追加

Navigationをプロジェクトに追加

プロジェクトにNavigationを追加していきます。

プロジェクト直下のbuild.gradleにNavigationのバージョン設定を追加します。
※2020/01/11 時点の最新バージョンの 2.1.0 を追加します。

buildscript {
    ext {
        nav_version = '2.1.0'
    }
}

app直下のbuild.gradleにNavigationの依存関係を追加します。

dependencies {
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

※依存関係を追加する箇所に直接バージョンを表記しても良いのですが、コンポーネントのバージョン管理を考慮してこのような書き方にしています。

SafeArgsをプロジェクトに追加

追加の前にSafeArgsを少し説明します。

SafeArgsは、画面遷移時に渡すデータに安全にアクセスできるビルダークラスを生成してくれます。
一般的に、画面遷移時はBundleを使用してデータの受け渡しをおこなっていると思いますが、SafeArgsを使うことで型安全なデータの受け渡しが可能になります

ということで、SafeArgsを追加していきます。

SafeArgsをプロジェクトに追加するには、プロジェクト直下のbuild.gradleに以下のclasspathを含めます。

buildscript {
    dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

app直下のbuild.gradleに以下を追加します。

apply plugin: "androidx.navigation.safeargs.kotlin"

フラグメントを追加

今回作成するサンプルでは、5つの画面を切り替えるサンプルを作るため、5つのフラグメントを追加します。
また、合わせてフラグメントに使うレイアウトファイルも追加しておきます。

フラグメントレイアウト
FirstFragment.ktfragment_first.xml
SecondFragment.ktfragment_second.xml
ThirdFragment.ktfragment_third.xml
ForceFragment.ktfragment_force.xml
FifthFragment.ktfragment_fifth.xml

ナビゲーショングラフを追加

画面遷移を定義するナビゲーショングラフを追加します。

Projectウィンドウでresディレクトリを右クリック -> New -> Android Resource Fileを選択
で、 New Resource File ダイアログが表示されます。

ダイアログ内の各項目は以下を設定します。

  • File name
    • nav_graph ※適当な名前を入力します
  • Resource type
    • Navigation
  • Directory name
    • navigation ※自動で挿入されます

その後、OKをクリックするとナビゲーショングラフを追加することができます。

これで追加したファイルは、resディレクトリ内にnavigationディレクトリが作成され、このディレクトリの中に作成されることになります。

ナビゲーショングラフに画面遷移を定義

画面の追加

ナビゲーショングラフに画面(フラグメント)を追加していきます。

ナビゲーショングラフのファイルを選択し、 New destination ボタンから一番最初に表示する画面を追加します。

同様に、残りの画面も全て追加していきます。

追加していくと、画面が重なって表示されるかと思いますが、 Auto Arrange ボタンを押すことで良い感じに自動整列してくれます。

画面レウアウトの適用

必須ではないのですが、このままだと画面にクラス名が書いてあるだけで、パット見どの画面なのか分かりづらいので、レイアウトに定義しているデザインが適用されるようにします。

タブを Design から Text に切り替え、xmlでの表示に変更します。

その後、それぞれの画面で使用するレイアウトを tools:layout で定義していきます。
※定義しただけではエラーとなるので、navigationタグの属性にxmlns:tools=”http://schemas.android.com/tools” を追加します。

その後 Design タブに切り替えると、画面が認識しやすくなります。

遷移の追加

画面の遷移を追加していきます。

遷移元の画面を選択し、上のタブから Add action ボタンを選択してactionウィンドウを表示します。
※遷移元画面を選択し、枠の右側に表示される○エリアをドラッグすることで遷移の線を追加することもできます。

ダイアログ内の各項目の設定は以下です。

  • ID
    • FromとDestinationから自動的に付けられるためこだわりがなければ入力不要です
  • From
    • 遷移元フラグメントを設定します
  • Destination
    • 遷移先フラグメントを設定します
  • Transition
    • 遷移する時のアニメーションを設定します(今回はデフォルト)

同様に、残りの画面も全て追加していきます。

Fifthへの画面遷移のみ Pop To にThirdを指定するようにします。
これが、FIfthで戻るボタンを押した時にThirdに戻るようにするための設定になります。

この時、Inclusiveにチェックをつけてしまった場合、Secondに戻るようになります。
Inclusiveを付けると、Thirdも含んでpopするようになるため、このような動作となります。

遷移を作成後、再度 Auto Arrange を使うとまた自動整列してくれて、遷移の作成は完了です。

フラグメントに遷移処理を追加

フラグメントに画面遷移の処理を追加していきます。

ここで一度ビルドを実行してください。
すると、自動でFirstFragmentDirectionsやSecondFragmentDirectionsなどのDirectionsクラスが作成されます。

このDirectionsクラスを使用してNavDirectionsを取り出し、findNavController().action() の引数に渡してあげるだけで簡単に画面遷移が実現できます。

class FirstFragment : Fragment(R.layout.fragment_first) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById(R.id.nextButton).apply {
            setOnClickListener {
                val action = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
                findNavController().navigate(action)
            }
        }
    }
}

Activityのレイアウトを調整

Activityのレイアウトを調整します。

Activityに適用しているレイアウトファイルを以下のように調整します。

....
<androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

<fragment 
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp" 
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        app:navGraph="@navigation/nav_graph" />
....

Toolbarは独自に作成するため、ActionBarはNoActionBarにしておきます。

fragment タグの重要な設定箇所は android:name  app:navGraph  app:defaultNavHost です。
android:name にはNavHostのクラスを設定します。
app:navGraph には作成したナビゲーショングラフを設定します。
app:defaultNavHost には true を設定します。

defaultNavHost属性を使用すると、システムの戻るボタンで前の画面に戻れるようになります。

また、Activityの処理を以下のようにします。

lass MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)

        val navController = findNavController(R.id.nav_host_fragment)
        val appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)
    }

    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.nav_host_fragment).navigateUp()
    }
}

また、この時下記のようなエラーが出た場合は少し調整が必要になります。

Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper ‘-jvm-target’ option

このようなエラーが出た場合はjvmのバージョンを変更しましょう。

Preferences > Other Settings > Kotlin to JVM > Target JVM version 1.8に設定

また build.gradle にも設定します。
これまでと同様にプロジェクト直下のbuild.gradleにバージョン設定を追加します。

buildscript {
    ext {
        jvm_version = '1.8'
    }
}

app直下の build.gradle にjvmのtargetバージョン設定を追加します。

android {
    kotlinOptions {
        jvmTarget = jvm_version
    }
}

SageArgsで画面遷移時にデータを受け渡す

SageArgsを使うことで安全にデータの受け渡しもできます。

SecondからThirdへの画面遷移時と、 ThirdからForceへの画面遷移時にデータの受け渡しをおこないます。
このとき、SecondからThirdへはIntegerのデータを受け渡し、ThirdからForceへは独自クラスのデータを受け渡すようにしたいと思います。

受け渡しデータをナビゲーショングラフに定義する

SecondからThirdへのデータ受け渡しを例にしていきます。

ナビゲーショングラフを開き、Thirdの画面を選択します。
画面右側のAttributesウィンドウでArgumentsの項目にある + ボタンをクリックし、 Add Argument Link ウィンドウを表示し、各項目に受け渡すデータの設定をします。

  • Name
    • 適切な変数名を設定します
  • Type
    • データの型を選択します
  • Default Value
    • 初期値を設定します

フラグメントにデータ受け渡しを処理を追加

フラグメントにデータ受け渡し処理を追加していきますが、ここで一度ビルドをしてください。

データを渡す側のフラグメントでDirectionsクラスを使ってNavDirectionsを取得するメソッドの引数に、先程追加したIntegerの値を渡すようになっているので、ここにデータを渡します。
データを渡す側はなんとこれだけ。

class SecondFragment : Fragment(R.layout.fragment_second) {

    companion object {
        private var counter = 0
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById(R.id.nextButton).apply {
            setOnClickListener {
                val action = SecondFragmentDirections.actionSecondFragmentToThirdFragment(++counter)
                findNavController().navigate(action)
            }
        }
    }

}

次はデータを受ける側の処理を作っていきます。

Fragment.navArgs() で自動生成されるArgsオブジェクトを取得し、これを介して渡されたデータにアクセスします。
Argsには、ナビゲーショングラフで定義した名前でプロパティにアクセスできます。
データを受け取る側もこれだけ。

class ThirdFragment : Fragment(R.layout.fragment_third) {
    ...
    private val args: ThirdFragmentArgs by navArgs()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        view.findViewById(R.id.messageView).apply {
            text = args.counter.toString()
        }
    }

}

※ThirdからForceへの遷移では、独自クラスを渡す必要があります。
独自クラスはSerializableを継承する必要がありますので、詳しくはサンプルアプリのコードを見てください。

完成版デモ


これにて、Navigationを使ってNEXTボタンを押すと次の画面に進み、戻るボタンを押すと一つ前の画面に戻る画面遷移を作ることができました!
また、 Fifthで戻るボタンを押した時には、期待通り一つ前の画面(Force)を飛ばして Thirdが表示される動きになっています。

SecondからThirdへの画面遷移時と、 ThirdからForceへの画面遷移時にはデータ渡しをおこなっていますが、こちらも渡せていることが確認できます。

最後に

Navigationコンポーネントはいかがでしたか。

視覚的に画面の遷移がわかりやすくなりました。
また、各所で beginTransactionreplacecommit などのトランザクションの処理も不要となりました。

簡単に画面遷移を管理できるので、新規で新しいプロジェクトを作成する際には積極的に取り入れていくのが良いと思います。