FirebaseDynamicLinksからAppLinksへの移行

# はじめに

:::note info
本記事は [ZOZO Advent Calendar 2024](https://qiita.com/advent-calendar/2024/zozo) のシリーズ10の13日目になります。
:::

私はFAANSというアパレルショップ向けのサービスにおけるAndroidアプリを担当しています。FAANSチームでFirebase Dynamic LinksからApp Linksへ移行するという対応を行ったのでそれについての内容を記載していきます。

# 本記事について

Firebaseの公式に以下の記載があり、Firebase Dynamic Linksが2025年8月25日に廃止されることがわかりました。

> 非推奨: Firebase Dynamic Links は非推奨となっており、このリンクがまだ使用されていないプロジェクトでは採用しないでください。サービスは 2025 年 8 月 25 日に廃止されます。詳しくは、ダイナミック リンクのサポート終了に関するよくある質問をご覧ください。

私が所属しているFAANSではブラウザからアプリへ遷移するディープリンクの機能をDynamic Linksを使って実装していたため代替となる別のサービスへ移行する対応が必要となりました。
また、FAANSでは強制アップデートの仕組みを導入していないためDynamic Linksが廃止されるよりも前に別サービスへ移行してそのアップデートを2025年8月25日を迎えるまでに浸透させることが必要となり、早めに対応することとなりました。

# Firebase Dynamic Linksについて
FIrebaseの公式ドキュメントにFirebase Dynamic Linksが提供する機能は以下と記載があります。
>Firebase Dynamic Links の機能:
>- ワンクリックで、デバイスに合ったストアにユーザーを誘導する（必要に応じてウェブページにフォールバック）
>- 遅延ディープリンクを使用して、アプリビューをダウンロードしインストールした後、ユーザーにプロセスの継続を提供する
>- アプリ内のディープリンク コンテンツを通じて、コンテキスト エクスペリエンスをユーザーに提供する（インストール済みの場合）
>- ダイナミック リンクのクリック イベントに関連する分析データを提供する
>- 短縮リンク URL を作成する機能を提供する
>- ソーシャル共有用のリンクにメタデータを追加できる機能を提供する

参考：https://firebase.google.com/support/dynamic-links-faq?hl=ja#what_should_i_do_if_i_need_full_feature_parity_with_firebase_dynamic_links

 # App Linksついて
 AndroidのDynamic Linksの移行先の候補として[App Links](https://developer.android.com/training/app-links?hl=ja)があります。
 Firebaseのドキュメントに以下の記載がありました。

>#### インストール済みのアプリへに対するディープリンクのみが必要な場合はどうすればよいですか？
>このシナリオでは、デベロッパーがユーザーにディープリンク エクスペリエンスを提供するためのプラットフォームでサポートされている、アプリリンクとユニバーサル リンクを使用することをおすすめします。
 
 App LinksはFirebaseのドキュメントにも[移行方法](https://firebase.google.com/support/guides/app-links-universal-links?hl=ja#app-links)が載っているAndroidが提供しているディープリンクの仕組みです。
 さまざまな検討を経てFAANSではインストール済みのアプリへに対するディープリンクのみの機能で要件を満たせていると判断しApp Linksを採用することになりました。(FAANS iOSではユニバーサルリンクを採用しました。)

 # App Linksへの移行手順

 ## 1. assetlinks.jsonを用意する

 
  以下のような形のassetlinks.jsonを用意し、[AndroidStudioでの検証](https://firebase.google.com/support/guides/app-links-universal-links?hl=ja#testing_app_links)で確認を行いました。

```.json:assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]
```
assetlinks.json 参考：https://developer.android.com/training/app-links/verify-android-applinks?hl=ja#web-assoc


 ## 2. アプリ側でApp Linksを受け取れるようにする

 ### AndroidManifestへの定義

[公式ドキュメント](https://developer.android.com/training/app-links?hl=ja#android-app-links)ではApp Linksを受け取るためのAndroidManifestへの定義として以下のコードが例として載っています。

```xml:AndroidManifest.xml
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <!-- Do not include other schemes. -->
    <data android:scheme="http" />
    <data android:scheme="https" />

    <data android:host="myownpersonaldomain.com" />
</intent-filter>

```

App Liknsに関する[Codelab](https://developer.android.com/codelabs/android-app-links-introduction?hl=ja#5)では以下が載っていました。

```xml:AndroidManifest.xml
<!-- Replace deep link and web link configuration with this -->
<!-- Please update the host with your own domain -->
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:scheme="https"/>
    <data android:host="example.com"/>
    <data android:pathPrefix="/restaurants"/>
</intent-filter>

```

autoVerifyには以下の説明があります
 >Android App Links, available on Android 6.0 (API level 23) and higher, are web links that use the HTTP and HTTPS schemes and contain the autoVerify attribute. This attribute allows your app to designate itself as the default handler of a given type of link. So when the user clicks on an Android App Link, your app opens immediately if it's installed—the disambiguation dialog doesn't appear.

 >Android 6.0（API レベル 23）以降で利用可能なAndroid アプリリンクはautoVerify、HTTP および HTTPS スキームを使用し、 属性を含むウェブリンクです。この属性を使用すると、アプリは特定の種類のリンクのデフォルト ハンドラーとして自身を指定できます。そのため、ユーザーが Android アプリリンクをクリックすると、インストールされている場合はアプリがすぐに開き、曖昧さ回避ダイアログは表示されません。

 `autoVerify=true`を設定することで対象のアプリがインストールされている場合以下のようなダイアログが表示されずにアプリが開いてくれます。

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2044482/3c692b60-afb3-b545-6e86-6192a5e3e0e3.png">

FAANSでは以下のような形で定義を行いました。

 ```xml:AndroidManifest.xml
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Handle "/home" path -->
    <data android:scheme="https"/>
    <data android:host="examlple.faans.no.domain.jp"/>
    <data android:pathPattern="/home"/>
    
    ・・・
</intent-filter>
```


</intent-filter>


 ディープリンクで遷移していた導線を整理し、それぞれ`pathPattern`で設定しました。

  ### MainActivityへの設定

  修正前のDynamic Linksのみの設定

  ```MainActivity.kt
Firebase.dynamicLinks
    .getDynamicLink(intent)
    .addOnSuccessListener(this) { pendingDynamicLinkData ->
        val deepLink = pendingDynamicLinkData?.link ?: return@addOnSuccessListener
            handlingDeepLink(deepLink)
    }
    .addOnFailureListener(this) { e ->
        ...
    }
  ```

  Dynamic LinksとApp Linksを両方とも受け取れる形の対応

  ```MainActivity.kt
  val intentArgs = intent.data
  if (
      intentArgs != null &&
      listOf(DEEPLINK_HOME, ..., ...).contains(intentArgs.path)
  ) {
      // AppLinksでの遷移
      handlingDeepLink(intentArgs)
    } else {
      // FirebaseのDynamicLinks
      Firebase.dynamicLinks
          .getDynamicLink(intent)
          .addOnSuccessListener(this) { pendingDynamicLinkData ->
              val deepLink = pendingDynamicLinkData?.link ?: intentArgs ?: return@addOnSuccessListener
              handlingDeepLink(deepLink)
          }
          .addOnFailureListener(this) { e ->
              ...
          }
  }
  ```
  App Linksの機能では`intent.data`からディープリンクで遷移してきたURLを取得することができるため、ハンドリングしたいURLが`intent.data`から取得できる場合はApp Links、そうでない場合はDynamic Linksから取得するようにしたことでどちらからのディープリンクにも対応できるようにしました。

 # 困った点

### 意図しないリンクでアプリへ遷移してしまった
pathPatternを特に設定しなかった場合、hostで設定したドメイン(上記の例であれば`examlple.faans.no.domain.jp`)を含むURLであればアプリへ遷移してしまっていました。ハンドリングしたいpathの定義を加えることで意図したURLのみの遷移に限定することができました。


### 特定のブラウザではディープリンクが機能しない
Samusung端末にデフォルトで入っている[Internet Browser](https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser&hl=ja)や[Arc Search](https://play.google.com/store/apps/details?id=company.thebrowser.arc&hl=ja)ではディープリンクの機能が発火せずブラウザへ遷移してしまいました。[Internet Browser](https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser&hl=ja)については画像のようにブラウザの設定である「ブラウザでアプリのリンクを開く」をオフにすると(デフォルトではオンだった)遷移させたいリンクでディープリンクを発火させることができました。
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2044482/bbdacd43-be22-9eff-21d3-f927c3942371.png">

こちらの制御に関してはブラウザ側の意思あるセキュアな制御であると判断し、FAANSの推奨ブラウザであるChromeでは設定不要でディープリンクの遷移ができていたこともあり、こちらの事象に関しては特に対応不要としました。

FAANSで利用されているChrome以外のブラウザとして多かったMicrosoftEdgeでは以下画像のようなダイアログが表示され「外部アプリを開く」を押下することでディープリンクの遷移を行うことができ、Firefoxでは画像のように外部アプリに対する設定を誘導する表示があり、こちらに関してはデフォルトの設定でディープリンクの導線へ繋げることができると判断しています。

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2044482/d83ac4fa-aa8a-598f-2658-f8099b1b80f1.png">
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2044482/b9bb328f-6b7f-c1a7-f918-e9287d4885fe.png">


 # 最後に
Firabase Dynamic Linksの廃止に伴いFAANSでは一時的に両機能が使える形でディープリンク機能の移行を実施しました。今回の対応ではFirebase Dynamic LinksとApp Linksの両方で動作を担保する必要があり動作確認のハードルの高さや片方がうまく動いてももう片方がうまく動かないなど苦戦する部分はあったのですが早い時期に対応することができました。
Firebase Dynamic Linksの廃止までまだ少し猶予がありますが、移行対象の方に本記事が少しでも参考になると嬉しいです！