見出し画像

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に遷移できるような構成です。

スクリーンショット 2020-05-02 19.17.16

<?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に遷移するようになっています。

画像2


では早速、この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"

画像3

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>

そして実際に動かしてみます。

画像4

きちんと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 としました。

スクリーンショット 2020-05-02 20.09.55

スクリーンショット 2020-05-02 20.11.13

<?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を発行してみます。

画像8

いい感じですね!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から起動した感じです。とてもいい感じになりました!

画像7

まとめ

- deep link起動時はBack stackにnavigation graphのstartDestinationが積まれる
- Splashやログインなどの起動フローとHome以降とでnavigation graphを分割しておくのがベター
- startDestinationが積まれていくので、起動時のFragmentも積まれることに注意。何かしらで頑張って対応する

この記事が気に入ったらサポートをしてみませんか?