Help us understand the problem. What is going on with this article?

Android Jetpack Component ~Navigation 編~

More than 1 year has passed since last update.

androidx についてネットを徘徊していたところ
面白そうな物を見つけたのでやってみました。

Navigation Architecture Component

Navigation Architecture Componentを使用すると、一般的ではあるが複雑なナビゲーション要件をアプリケーションに実装できるので、一貫した予測可能なエクスペリエンスをより簡単にユーザーに提供できます。

Navigation  |  Android Developers

要するに ナビゲーション=画面遷移 が簡単にできる様になったよって事なのかな🤔

No more fragment transaction!!!

めっちゃ書かれてました。

どれだけ簡単になるのか。
Fragment の遷移をやってみたいと思います。

環境

今回使用した環境です。
Navigation を使用するためには Android Studio 3.2 以上が必須です。

  • Android Studio 3.2.1
  • Kotlin 1.2.71

navigation の編集をするために
Preference -> Experimental -> Editor から
Enable Navigation Editor にチェックが入っていなければチェックして再起動しましょう。

build.gradle

現在の最新版は 1.0.0-rc02 ですが、最新版にすると gradle 3.3 が必須になるらしく
今回の環境でやろうとすると R ファイルが見つからなかったりワーニング出たりとよくわからん事象が発生してたので少しバージョン落としてます😭

これか...??Android Studio のバージョン上げればちゃんと動くのかな...🤔

project/build.gradle
buildscript {
    dependencies {
        ...

        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha11"
    }
app/build.gradle
apply plugin: "androidx.navigation.safeargs"

dependencies {
    ...

    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha11'
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'
}

画面遷移するだけなら必要なのは

implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha11'
implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha11'

ここだけです。

safeargs については後で出て来ますので必要であれば。。。

Navigation

Resource

まずはリソースファイルの作成をします。

いつも通りに Android Resource File を新規作成します。
ただし、今回選択する Resource Type は Navigation を選択します。

Enable Navigation Editor にチェックが入ってなければ選択できない様なので
選択肢に出てこない場合は設定しましょう。

名前はなんでもいいです。
今回は Get Started に書いてあるので nav_graph.xml にしました。

OK すると res/navigation ディレクトリが作成されてこんな感じの xml ができあがります。

nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android">

</navigation>

a04ae75b.png

Transition

Design タブの左上に + アイコンがあり、そこから Fragment を追加します。

Fragment はいつも通りに作成したものを準備しました。

  • FirstFragment
  • SecondFragment

+ アイコン から Create Blank Destination を選択すると Fragment の作成もできます。
Text タブから xml 直書きもできます。
今回はどんな風に出来上がるのかを見るために GUI で動かしていきますね。

まずは Fisrt Fragment を追加してみましょう。

nav_graph.xml
<!-- layout を追加すると Design で Fragment 内のレイアウトが確認できる -->
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          tools:layout="@layout/fragment_first"/>

fragment タグは新鮮な感じがしますねー。
同じ様に SecondFragment も追加します。

2つ揃ったらいよいよ遷移させてみます。
FirstFragment から矢印を SecondFragment に向けて引っ張るだけです。

a8984681.png

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          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.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second"/>

action タグが追加されてますね。
これで準備は整いました。

後は遷移させたいタイミングで

binding.button.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment)
}

みたいにやると SecondFragment に遷移します。

最終的に xml 内はこうなります。
navigation タグに startDestination を追加すると初期表示画面を選択できます。

action タグに transition を設定するとアニメーションの設定ができたりします。

nav_graph.xml
<?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.taku.navigation.fragments.FisrtFragment"
              android:label="FisrtFragment"
              tools:layout="@layout/fragment_fisrt">

        <action android:id="@+id/action_firstFragment_to_SecondFragment"
                app:destination="@id/SecondFragment"/>

    </fragment>

    <fragment android:id="@+id/secondFragment"
              android:name="com.taku.navigation.fragments.SecondFragment"
              android:label="SecondFragment"
              tools:layout="@layout/fragment_second"/>
</navigation>

後は Activity の xml 内で fragment タグを追加します。
name には NavHostFragment を指定します。
NavHostFragment の中身を入れ替えて画面遷移を実現している様ですね。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

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

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

これでアプリを起動すれば FirstFragment からスタートします。

FragmentManager が出てこない!!しゅごい!!

実際の遷移処理は FragmentNavigator がやってくれています。

findNavController で渡された View から親を辿って NavController を探し出すみたいですね。
この NavController に nav_graph.xml が bind されてグラフに基づいて navigation を管理してくれているんですね。

NavController が Navigator に繋ぎます。
navigate 関数の中で className から instantiate で Fragment を作って FragmentManager.beginTransaction.replace ってやってます。
NavOptions を使ってアニメーションの設定もゴリゴリやってくれています。

要するに今回の Navigation ライブラリは Fragment 遷移に関して言うと今まで頭を捻っていた Transaction をモダンな仕様でいい感じにラッピングしてくれているんでしょう。

  • popBackStack

Back ボタンに対応させる時は Activity で

override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()

ってやると内部で popBackStack() してくれてるっぽいので画面が戻ります。

バックスタックの管理もできます。

app:popUpTo="@id/firstFragment"

ってやると指定した Fragment から上に積まれてる Fragment を pop してくれます。

Safe args

最初に入れた safeargs とか言う怪しいプラグインはなんだったのか。
先に言うとめっちゃ感動します。

順番に見て行きましょう。

まず、fragment タグの中に argument タグが入れれるんですね。
これで Bundle の受け渡しができます。
どんな感じでやるかというと

nav_graph.xml
<fragment android:id="@+id/firstFragment"
          android:name="com.taku.navigation.fragments.FisrtFragment"
          android:label="FirstFragment"
          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.taku.navigation.fragments.SecondFragment"
          android:label="SecondFragment"
          tools:layout="@layout/fragment_second">

        <argument android:name="message"
                  android:defaultValue="default"
                  app:argType="string"/>
</fragment>

あとは遷移元で navigate の引数に Bundle を渡してあげると
遷移先でいつものように arguments から取得できます。

// from
class FirstFragment {
    ...
    val bundle = bundleOf("message" to "to destination")
    Navigation.findNavController(it).navigate(R.id.action_firstFragment_to_secondFragment, bundle)
}

// to
class SecondFragment {
    ...
    val message = arguments?.getString("message") ?: ""
}

...すてきやん?

個人的に今回の中で一番感動してます。
わざわざ newInstance 関数生やして setArgmunets してインスタンス返さなくてもよくなった!スッキリ!

さて、これだけでも結構感動してるんですけどここから涙がちょちょぎれます。

上のやり方って、key の管理とか type が String だから getString 使うとか色々と大変なこと残ってますよね。
デフォルト値設定できるのに arguments が Nullable やし...

そんな時に使うのが Safe args !!

これを使うとどう変わるのか。デデーン

// from
class FirstFragment {
    ...
    Navigation.findNavController(it).navigate(
        FirstFragmentDirections.actionFirstFragmentToSecondFragment().apply {
            message = "to destination"
        }
    )
}

// to
class SecondFragment {
    ...
    val message = SecondFragmentArgs.fromBundle(arguments ?: return).message
}

٩(๑❛ᴗ❛๑)۶

ん〜!!!!
煩わしいことがなくなって超絶スッキリ。

ここでのポイントは DirectionsArgs。付けられた ID を元に自動生成されます。
めっちゃわかりやすくなりますよね。各 Fragment にそれぞれ Directions と Args のサフィックスがつくだけです。
使う時も名前が入ってる方って覚えておけばどっちだっけってならなくなります。

で、遷移時の navigate() の引数気づきました?
Action の ID と Bundle じゃなくて Directions しか渡してない んですよね。
渡す ID を間違えた!なんてことがなくなります。

これすごくないですか。
Args で取得したメンバーは補完が効いててキャストいらないし、設定もメソッドを選ぶだけで key 違うじゃんとかもない。
arguments 使わなくても Directions 渡せば遷移できちゃうので乱用しそう。

これだけのために Navigation 入れてもいいんじゃないかな。。。
Percelable なクラスしかやりとりできないんですけど Json ライブラリとか入れれば data class のやりとりもできそうだよね。

こんな事もできちゃうみたい。可能性しか感じていない。

感想

すっごい、Xcode のストーリーボードだなって...
ただストーリーボードの xml と違ってややこしくないから Text から直書きマンには嬉しいです。

わざわざ Activity にイベント通知して FragmentManager 呼んで Transaction 準備して Commit して...
って煩わしい処理がなくなったので、謳い文句通り簡単に遷移ができてますね。
チームでの開発とかでも Transaction の管理とかで変な差が出にくくなりそうだからいいのかも?🤔

Safe args はほんと神だと思うので積極的に使って行きたい!!stable 版に期待大!!
Jetpack 自体がですけどリリースノートを見る限り現状かなり頻繁に更新されてますね。

DeppLink とか navigation タグのネストとか他にもありそうなので色々やってみたいなー。

参考

Navigation Codelab

Android: Navigationのsafeargs Gradle pluginだけを使ってもいいかもしれない - stsnブログ

Navigation Architecture Componentを試してみる - Qiita

tick-taku
Android えんじにゃー。猫とロードバイクが好きです。 気になったことや知ったことを吸収するために、少しずつ書いていこうと思います。 #Android #Kotlin #AWS #Python
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away