Deep link with NavigationではNested navigation graphを活用しよう
最近のAndroidの画面遷移はNavigationを使って定義することができるようになりました。しかも、deep linkもNavigationでFragmentを指定すればよしなに処理してくれます。しかし、deep linkを開いたあとのBack stackにFragmentを積んでいくにはNested navigation graphをうまく活用する必要があります。と言うお話です🚀
今回のブログを書くにあたってサンプルを作りました😍これをベースにして書いていきます🙋♂️
https://github.com/yasukotelin/DeeplinkWithNavigation
作ったサンプルアプリのnavigation.xmlはこんな感じです。最初にSplash画面があって、次にHomeの画面。そこからボタンでDetailとNoticeに遷移できるような構成です。
<?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"
app:startDestination="@id/fragment_splash">
<fragment
android:id="@+id/fragment_splash"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.splash.SplashFragment"
android:label="fragment_splash"
tools:layout="@layout/fragment_splash">
<action
android:id="@+id/action_splash_to_home"
app:destination="@id/fragment_home"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/fragment_splash"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/fragment_home"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_main_to_detail"
app:destination="@id/fragment_detail"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_main_to_notice"
app:destination="@id/fragment_notice"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<fragment
android:id="@+id/fragment_detail"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.detail.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" />
<fragment
android:id="@+id/fragment_notice"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.notice.NoticeFragment"
android:label="fragment_notice"
tools:layout="@layout/fragment_notice" />
</navigation>
アプリを実際に動かした感じがこんな感じ。Splash画面はアニメーション終了時に自動でHomeに遷移するようになっています。
では早速、このnavigation graphにdeep linkを追加してみましょう。
パターン1
example://deeplink/home カスタムURLを踏んだらHomeを開くdeep linkを追加してみます。
fragment_homeにdeeplinkタグを追加してuriを指定するだけ。とても簡単ですね。
<fragment
android:id="@+id/fragment_home"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<deepLink
app:uri="example://deeplink/home" />
<action
android:id="@+id/action_main_to_detail"
app:destination="@id/fragment_detail"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_main_to_notice"
app:destination="@id/fragment_notice"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
AndroidManifestにnavigation graphを登録するのを忘れないように(いつも忘れる
<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" />
</activity>
deeplinkの発行はTerminal上からadbを使って発行します。
adb shell am start -a android.intent.action.VIEW -d "example://deeplink/home"
Terminalからdeep linkを発行しているのでちょっとわかりにくいですが、ちゃんとHomeが起動しています。いい感じですね!ただ、BackしたときになぜかSplashが起動しています🤔 これはちょっといけません。
パターン2
今度は、 example://deeplink/notice カスタムURLを踏んだらNoticeを開きます。Homeと同様に、deeplinkタグを指定するだけです。
<fragment
android:id="@+id/fragment_notice"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.notice.NoticeFragment"
android:label="fragment_notice"
tools:layout="@layout/fragment_notice">
<deepLink
app:uri="example://deeplink/notice" />
</fragment>
そして実際に動かしてみます。
きちんとNoticeの画面が開いていますが、その後の戻った時の挙動がだいぶ変な動きをしていますね。期待する挙動としては、Noticeから戻ったときはHomeが表示されて欲しいですが、実際はNoticeから戻るとSplashが表示されてしまいます。
Back Stackに積まれるのはstartDestination
deep linkで直接Fragmentに飛んだ場合、よしなにBack StackにFragmentを積んでくれるのですが、このとき積まれるFragmentはnavigation graphのstartDestinationです。
つまり、今回で言えば単一のnavigationのstartDestinationであるSplashFragmentが積まれているわけです。
この仕様のため、deep linkを適切に処理しようと思うとnavigation graphををネストして書いていく必要があります。最低限、Splashやログインなどの起動フローと、Home以降のフロートでnavigation graphを分割しておく必要があるでしょう。
Nested navigationにする
Splash部分とHome以降の部分を分割してHome以降をNested graphにします。Splash部分を navigation_launch それ以降の部分を navigation としました。
<?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_launch"
app:startDestination="@id/fragment_splash">
<fragment
android:id="@+id/fragment_splash"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.splash.SplashFragment"
android:label="fragment_splash"
tools:layout="@layout/fragment_splash">
<action
android:id="@+id/action_splash_to_home"
app:destination="@id/navigation"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/fragment_splash"
app:popUpToInclusive="true" />
</fragment>
<navigation
android:id="@+id/navigation"
app:startDestination="@id/fragment_home">
<fragment
android:id="@+id/fragment_home"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.home.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
<deepLink app:uri="example://deeplink/home" />
<action
android:id="@+id/action_main_to_detail"
app:destination="@id/fragment_detail"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_main_to_notice"
app:destination="@id/fragment_notice"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
<fragment
android:id="@+id/fragment_detail"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.detail.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" />
<fragment
android:id="@+id/fragment_notice"
android:name="com.github.yasukotelin.deeplinkwithnavigation.ui.notice.NoticeFragment"
android:label="fragment_notice"
tools:layout="@layout/fragment_notice">
<deepLink app:uri="example://deeplink/notice" />
</fragment>
</navigation>
</navigation>
この状態でNotice画面へdeep linkを発行してみます。
いい感じですね!NoticeからHomeへ戻れています。startDestinationになっているHomeFragmentが積まれていることがわかりますね。ただ、Homeから戻ったときにSplashが表示されてしまっている課題はクリアできていません。これはstartDestinationにSplashも指定されているので、deep linkから起動したときにはSplashをStackからPopできていないためです。
そのため、何かしらの方法で対策をする必要があります。今回はHomeから戻るときはアプリを終了するようにしました。androidxからfragment上で簡単にBackキーの制御ができるようになったので、いちいちActivityにListenerを実装しなくて楽チンです。
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
}
)
これでHomeからBackキーを押したときはアプリが終了します。シングルActivityだからこそできる簡単技ですね。
Noticeをdeep linkから起動した感じです。とてもいい感じになりました!
まとめ
- deep link起動時はBack stackにnavigation graphのstartDestinationが積まれる
- Splashやログインなどの起動フローとHome以降とでnavigation graphを分割しておくのがベター
- startDestinationが積まれていくので、起動時のFragmentも積まれることに注意。何かしらで頑張って対応する
この記事が気に入ったらサポートをしてみませんか?