白眼鏡のblog

新しく得た知見を備忘録的に書き連ねていく

Navigation Architecture Component使用方法メモ

Navigation Architecture Component(以降Navigation)について、しばらく使ってみて得た知見をまとめます。

導入方法

Navigationを導入するために必要なライブラリを読み込みます。
gradleの設定
build.gradle(project)

buildscript {  
    ...  
    dependencies {  
        ...  
        // Navigationでの遷移に利用するSafeArgsを読み込む  
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
    }
}

build.gradle(module)

apply plugin: 'androidx.navigation.safeargs.kotlin'
// SafeArgsの読み込み、Javaコードの場合は最後の.kotlinを取り除く

dependencies {
    ...
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
    ...
}

以上の記述を追加します。versionについては適宜読み替えてください。

基本的な操作

基本的な画面遷移の記述方法について記述します。
Projectのresを右クリック→New→AndroidResourceFileから
File name: navigation_graph(任意の名前)
Resource type: Navigation
を設定し、画面遷移の情報などを記述するNavigationResourceを作成します。

NavigationResourceのText画面を開き以下の内容を記述します。

<?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/navigation_graph"
            app:startDestination="@id/firstFragment">
<!--Navigation読み込み時に最初に呼ばれる画面をapp:startDestinationで設定する-->
<!--呼ばれるFragment-->
    <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first"/>

対象になるActivityのレイアウト内にあるFragmentタグ内に以下の記述を追加します。

        <fragment
                ...
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:navGraph="@navigation/navigation_graph"
                app:defaultNavHost="true"/>

android:nameで、NavHost実装のクラスを読み込みます。
app:navGraphで読み込むNavigationResourceを指定し、app:defaultNavHosでがシステムの[戻る]ボタンを傍受することを許可します。
ここまでの記述を行うことで、対象Activity表示時にNavigationGraphのstartDestinationに設定したFirstFragmentfが表示されることが確認できます。

別Fragmentへの遷移

まずは、NavigationGraphに遷移先となるFragmentを追記します。

<?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/navigation_graph"
            app:startDestination="@id/firstFragment">
<!--Navigation読み込み時に最初に呼ばれる画面をapp:startDestinationで設定する-->
<!--呼ばれるFragment-->
    <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first"/>

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

追加したSecondFragmentへFirstFragmentから遷移できるようにActionを追記します。

...
    <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first">
        <action
                android:id="@+id/action_first_to_second"
                app:destination="@id/secondFragment"/>
    </fragment>
... 

最後に、FirstFragmentからSecondFragmentへの遷移を呼び出す処理を記述します。
FirstFragment.kt

class FirstFragment: Fragment() {
    ...
    fun onClickNext(view: View) {
        // NavControllerを利用し次の画面へ遷移する
        findNavController().navigate(FirstFragmentDirections.actionFirstToSecond()
    }
}

これでonClickNextメソッドが実行されると、SecondFragmentへ遷移します。

応用的な操作

遷移先のFragmentへ値を渡す。

遷移先のFragmentへ値を渡すために、まずnavigation_graphへargumentを設定します。

    <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">
        <argument
                android:name="code"
                app:argType="string"/>
        <argument
                android:name="state"
                app:argType="string"/>
    </fragment>

設定した段階で一度ビルドしておきます。 ビルド後にFirstFragment.ktの画面遷移を記述した箇所を以下のように変更します。

class FirstFragment : Fragment() {
    ...
    fun onClickNext(view: View) {
        // NavControllerを利用し次の画面へ遷移する
        findNavController().navigate(FirstFragmentDirections.actionFirstToSecond(
            code = "code"
            state = "state"
        )
    }
}

これで、遷移時に値を付加することができます。
受け取り側は、以下のようにして値を取得します。

class SecondFragmen : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        ...
// navArgs経由でFirstFragmentで設定した値を取得します。
        val args: FirstFragmentArgs by navArgs()
        val code = args.code
        val state = args.state
        ...
    }
}

DeepLink

特定のFragmentをURLSchemeで起動するためにはnavigation_graphの起動したいfragmentにいかの記述を追記する必要があります。

    <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">
        <argument
                android:name="code"
                app:argType="string"/>
        <argument
                android:name="state"
                app:argType="string"/>
        <deepLink app:uri="example://navigation/sample"/>
    </fragment>

また、AndroidManifest.xmlに以下の記述を追記します。

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <nav-graph android:value="@navigation/navigation_graph" />
        </activity>

ここまでの設定を記述した上でMergedManifestを確認するとintent-filterタグ内のdataタグに設定したuriが表示されていることが確認できます。
また、fragment内でURLSchemeのパラメータを取得したい場合、navigation_graph内で設定したargumentにマッピングすることができます。

    <deepLink app:uri="example://navigation/sample"/>  

の箇所を

    <deepLink app:uri="example://navigation/sample{code}"/>  

とすることで、URLのsample以下の文字列を全て、変数codeに格納することができます。
しかし、一般にパラメータとして想定されるものは
?code=hogehoge&state=fugafuga
のような形になるため、このまま使用すると変数code内に?code=hogehoge&state=fugafugaが格納されることになります。
これでは使いづらいため、以下のように設定することで特定の箇所のみ変数に格納することができるようになります。

    <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second">
        <argument
                android:name="code"
                app:argType="string"/>
        <argument
                android:name="state"
                app:argType="string"/>
        <deepLink app:uri="example://navigation/sample?code={code}&amp;state={state}"/>
    </fragment>

こうすることで、変数codeにはhogehoge、stateにはfugafugaが格納されることになります。

条件付きで表示される画面

スプラッシュ画面やログイン画面など、startDestinationに設定するわけではないが条件に応じてアプリ起動時に表示される画面について、どう実装すべきかと言うと公式で

Conditional navigation  |  Android Developers

と言うものが用意されています。 まずは、呼び出すSplashFragmentをnavigation_graphに追加します。

    <fragment
            android:id="@+id/splashFragment"
            android:name="com.example.SplashFragment"
            android:label="fragment_splash"
            tools:layout="@layout/fragment_splash"/>

一度ビルドし、以下のようにFirstFragmentを編集します。

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

        // 適当なアプリ状態判定
        if (status == Status.UNAUTHENTICATED) {
            findNavController().navigate(R.id.firstFragment)
        }
    }

このようにonViewCreated内でSplashFragmentを呼び出します。
一瞬TopFragmentが表示されBackStackに積まれるような予感がしますが、そんなことはなく戻るボタンでアプリが終了するといった動きになります。

戻るボタンで戻らせたくない画面への対応

例えば、ログイン画面など一時的に表示し、戻るボタンで戻したくない画面などでは以下のような設定をすることでbackstackへ積まないようにすることができます。

...
    <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first">
        <action
                android:id="@+id/action_first_to_second"
                app:destination="@id/secondFragment"
                app:popUpTo="@id/secondFragment"
                app:popUpToInclusive="true"/>
    </fragment>
... 

この設定をした上でfirst→secondと遷移した後戻るボタンをタップするとfirstの前に表示していた画面に戻る。といった動作をします。