blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2023.05.25 技術記事

Firebase Dynamic Links ディープリンク

by Teruo Suzuki

#allm #mysos #ios #firebase #dynamic-links #deep-link

はじめに

こんにちは、株式会社アルム 研究開発部 メディカルソリューション開発グループの鈴木です。

iOSエンジニアとして救命・健康サポートアプリ MySOS を開発しています。

今回は、私が担当しているMySOSで利用しているFirebase Dynamic Linksのディープリンクについてご紹介したいと思います。


さっそくですが、Firebase Dynamic Links ディープリンクについて説明します。

Firebase Dynamic Links ディープリンクとは、アプリ内の特定のコンテンツへ直接リンクできるURLのことです。リンクをタップすることで提供したい特定のサービスへと直接リンクすることができます。例えば、アプリ内の数あるコンテンツの中からお気に入りの動画や商品ページなどをリンクをタップすることで、直接そのページへと導くことができます。

同じURLを使用して、iOS、Android、Webプラットフォームなど、複数のプラットフォームで動作するため、各プラットフォームごとにURLを分ける必要がありません。

また、アプリがインストールされていない場合でも、iOS端末ならばApp Store、Android端末ならばPlay ストアへと誘導しインストールを促します。そしてインストール プロセスを挟んだとしてもディープリンクは途切れずに引き続き目的のコンテンツへと誘導することができます。

以下の図のように、URLをQRコードにすることで更に簡単に誘導することができます。

ちなみに、iOSではUniversal Links、AndroidではApp Linksといった機能があり、Firebase Dynamic Linksディープリンクはこれらの機能を一本化したものとなります。


短縮URL

MySOSでは、Firebase Dynamic Linksを2つの方法で利用しています。1つ目は今回紹介するディープリンク、もう1つは短縮URLの生成です。

Googleが提供していたURL短縮サービス「Google URL Shortener」が2019年3月に終了したことで、Firebase Dynamic Linksを代替として利用する方が増えたのではないでしょうか? 私もその1人で、Firebase Dynamic Linksを利用したのは最初は短縮URLでした。


費用

無料。


MySOSにディープリンクを導入したときの話

ディープリンク機能を初めて利用したのは、約3年前で、MySOSにある機能を追加した際のことです。スポーツイベントに参加された方々に向けて、MySOSをインストールしていただき、イベント会場内で新型コロナの感染者が発生した場合に迅速に連絡するための機能を提供しました。MySOSは通常の利用において、ユーザ登録は必須ではありません。しかし、イベント来場者を特定し、サービスを提供するためには、ユーザ登録に加えて、参加するイベントの選択などを行っていただく必要がありました。このとき、プロジェクトマネージャーから「アプリがインストールされていない状態からイベント登録までの手順をシームレスに行えないと、問い合わせが増加する可能性がある。Firebase Dynamic Linksディープリンクを使うことで解決できる」という話を聞いて、そのときこの機能を知りました。話を聞いたときは、「アプリをインストールした後は、さすがにもう一度リンクをタップしないと、リンクに含まれた情報がアプリに伝わらないのでは?」と半信半疑で調べ始めたことを覚えています。


ディープリンク作成

ディープリンクのURLの作成方法には手動で作成する方法の他に、Firebase ConsoleやREST APIからも作成することができます。Firebase ConsoleやREST APIで作成した場合は、URLを短縮して生成してくれます。今回は手動での作成方法を紹介します。

以下のように、ドメインを用意して各パラメータを設定していきます。

     
    https://yourapp.page.link/?
    link=https://www.hoge.net/hoge/?param_1=XXXX&param_2=XXXX
    &apn=com.allm.android
    &ibi=com.allm.ios
    &isi=971663851
    &ius=com.allm.ios
    &imv=3.0.0
     

パラメータ

パラメータは以下のようなものがあります。

共通パラメータ
link URLエンコードされたURLを指定します。スマートフォン上でディープリンクをタップした場合、このURLがアプリに渡され、アプリ起動が行われます。パソコン上でディープリンクをクリックした場合は、ここで指定したWebページが表示されます(ofl パラメータが指定されている場合を除く)。
Android用パラメータ
apn Android アプリのパッケージ名。
afl アプリがインストールされていない場合に開くリンク。
amv リンクを開くことができるアプリの最小バージョンの versionCode。
iOS用パラメータ
ibi iOS アプリのバンドル ID。
ifl アプリがインストールされていない場合に開くリンク。
ius アプリのカスタム URL スキーム(アプリのバンドル ID とは異なるものを定義している場合)。
isi アプリの App Store ID。
imv リンクを開くことができるアプリの最小バージョンのバージョン番号。
その他のプラットフォーム用パラメータ
ofl Android と iOS 以外のプラットフォームで開くリンク。

MySOSでは、linkパラメータには、MySOSのWebページ(https://www.allm.net/mysos/)のURLを指定しています。

これにより、パソコン上でURLをクリックした場合にはMySOSのWebページが表示されるようになります。

また、 先程の例 のように、 linkに指定するURLにパラメーター(param_1param_2など)を追加することで、アプリがリンク情報を受け取った際に、より具体的なコンテンツへの誘導を行うことができます。


ドメイン

ドメインは、自分で用意することもできますが、Firebase Dynamic Linksでは、無料で提供しているドメイン(例: yourapp.page.link)が用意されています。そのドメインを作る手順を説明します。なお、Firebaseプロジェクトがすでに作成済みであることを前提として、以下に手順を説明します。


まず、 Firebase Console にアクセスし、メニューから「Dynamic Links」を選択し、「始める」ボタンをクリックします。


次に、「URL接頭辞の追加」ポップアップが表示されるので、URL接頭辞(Prefix)を入力し「続行」ボタンを押します。


次に、以下のようなポップアップが表示されますので「終了」を押します。


これで、入力したURL接頭辞のドメインが利用可能となります。

作成したドメインの後にパラメータを組み合わせてディープリンクを作成しましょう。完成したURLは、更に短縮することをおすすめします。


アプリへの導入手順

次に、作成したディープリンクがアプリ側で受け取れるようにするための手順を説明します。ここではiOSの導入手順を記載します。


(1)Firebaseプロジェクトにアプリ登録する

Firebaseプロジェクトにまだアプリが登録されていない場合は、次のような画面が表示されます。その場合は 「iOS+」ボタンをクリックしてアプリを登録しましょう。


すると、次のような画面が表示されますので、必要な情報を入力します。ステップ①からステップ⑤までの手順がありますが、ステップ①でアプリの登録が完了します。


ステップ②では、「GoogleService-Info.plist」をダウンロードして、Xcodeプロジェクトに追加します。


ステップ③では、XcodeプロジェクトにFirebase SDKを追加する方法が記載されています。 以前はCocoaPodsを利用した説明でしたが、現在はSwift Package Managerに変わっています。 CocoaPodsを利用する場合は、画面上部のCocoaPodsのリンクから手順を確認してください。 また、ここでの説明にはDynamic Linksの追加手順は含まれていませんので、Swift Package Managerを利用する場合はライブラリ選択の際に「Dynamic Links」も選択し、CocoaPodsを利用する場合はPodfileに「pod ‘FirebaseDynamicLinks’」を追記してください。

なお、CocoaPods を使用する場合、Firebase Pod の最新版は v9.0 以降では非推奨となっています。

代わりに、Podfile 内でプロダクトの Pod を直接参照することが推奨されており、例えば Analytics なら ‘FirebaseAnalytics'、Dynamic Links なら ‘FirebaseDynamicLinks’ のように記述します。つまり、以前はPodfileに「pod ‘Firebase/DynamicLinks’」と書いていましたが、現在は「pod ‘FirebaseDynamicLinks’」と書くように推奨されています。



ステップ④では、初期化コードの追加になります。 Firebaseのドキュメントは、各機能の導入ドキュメントにSwiftUI、Swift、Objective-Cのコード例がありますので、実装に困ることはあまりないかと思います。


ステップ⑤は、完了画面です。「コンソールに進む」を押して終了。



(2)FirebaseプロジェクトのアプリにチームIDを設定する

次にFirebaseプロジェクトに追加したアプリの設定にチームIDを追加します。 まず、「プロジェクトの概要」の横にある設定アイコンをクリックし、「プロジェクトの設定」を選択してください。 するとプロジェクトの設定画面が開き、いくつかのタブが表示されます。 その中から「全般」タブを選択し、下にスクロールしていくと、「Appleアプリ」という項目が表示されます。 先ほど手順で追加したアプリの情報が表示されますので、「チームID」を設定してください。「チームID」とありますが、Apple Developer Program のTeamIDを設定するのではなく、Identifiersの「App ID Prefix」を設定してください。



(3)Xcodeプロジェクト設定

次に、Xcodeプロジェクトの設定を行います。前の手順で「GoogleService-Info.plist」の追加やFirebase SDKのインストールが完了しているため、ここでは説明を省略します。


以下の手順で「URL Types」を追加します。

  1. Xcode プロジェクトのTARGETから「Info」タブを選択します。
  2. URL Types」を選択し「+」ボタンから新しいURL Typesを追加します、
  3. URL Types」の「URL scheme」には、バンドルIDを設定します。
  4. URL Types」の「identifier」には、端末で一意な値を設定します。ここでもバンドルIDを設定すると良いでしょう。

次に、以下の手順で「Associated Domains」を追加します。 「Associated Domains」は、Firebase Consoleで追加したドメイン(xxxxxx.page.link)を設定します。

  1. Xcode プロジェクトのTARGETSを選択。
  2. Signing & Capabilities」を選択し、「+Capabilities」をクリックします。Capabilitiesの選択画面が開きます。
  3. 選択画面から「Associated Domains」を探してダブルクリックし、TARGETSに追加します。
  4. 追加された「Associated Domains」のDomainsにある「」ボタンをクリックします。 デフォルトではwebcredentials:example.comのようにドメイン名が追加されますので、「applinks:xxxxxx.page.link」のように変更してください。ドメインは、先ほどFirebase Consoleで追加したドメインを設定してください。

Associated Domains」が追加されると、Xcodeプロジェクトには自動的にentitlementsファイルが追加されます。 ただし、すでにentitlementsファイルがある場合は、そのファイルに「Associated Domains」が追加されます。 entitlementsファイルは、先ほどCapabilitiesを追加した画面と同期しているため、このファイルからもドメインの追加や編集が行えます。


(4)コードを書く

ディープリンクをアプリで受け取る準備は完了しました。あとはAppDelegateにコードを追加するだけです。


  • Firebase モジュールをインポートします。
import FirebaseCore
import FirebaseFirestore
import FirebaseAuth
// ...

  • application(_:didFinishLaunchingWithOptions:) メソッドに以下を追加します。
// Use Firebase library to configure APIs
FirebaseApp.configure()

  • application:continueUserActivity:restorationHandler: メソッドを実装します。 このメソッドは、既にアプリがインストールされている状態でディープリンクが実行された場合に呼び出されるメソッドです。 dynamicLink変数にはディープリンクURLのlinkパラメータで指定したURLが含まれます。 このURLを使って、ユーザーを誘導すべきコンテンツを開くように実装します。
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    let handled = DynamicLinks.dynamicLinks()
    .handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in
        // ここにディープリンクを受け取ったときのコードを書きます。
    }

    return handled
}

  • 次に application:openURL:options: メソッド と application:openURL:sourceApplication:annotation: メソッドを実装します。 application:openURL:options: は、アプリがインストールされていない状態でディープリンクがタップされたとき、アプリの初回起動時にコールされるメソッドです。 application:openURL:sourceApplication:annotation: application:openURL:options: からコールされます。 application:openURL:sourceApplication:annotation: に実装されているdynamicLink変数にはディープリンクURLのlinkパラメータで指定したURLが含まれます。 このURLを使って、誘導すべきコンテンツを開くように実装します。
@available(iOS 9.0, *)
func application(_ app: UIApplication, open url: URL,
                 options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool 
{
    return application(app, open: url,
                     sourceApplication: options[UIApplication.OpenURLOptionsKey
                       .sourceApplication] as? String,
                     annotation: "")
}

func application(_ application: UIApplication, open url: URL, sourceApplication: String?,
                 annotation: Any) -> Bool 
{
    if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url){
        // ここにディープリンクを受け取ったときのコードを書きます。
        return true
    }
    return false
}



注意点

注意点(1つ目)

Firebaseプロジェクトに「チームID」を設定する説明の箇所でも触れましたが、注意点として、「チームID」には、Apple Developer ProgramのTeamIDを設定するのではなく、Identifiersの「App ID Prefix」を設定する必要があります。 Identifiersを生成する際、ほとんどの場合は「App ID Prefix」としてApple Developer ProgramのTeamIDが設定され、それに基づいてIdentifiersが生成されることが多いと思います。そのため、ほとんどの場合は「チームID」にTeamIDを設定しても問題ありません。 しかし、私が最初に試したアプリでは、Identifiersの「App ID Prefix」がTeamID以外のIDを選択してIdentifiersが生成されていました。 Firebaseの入力欄には「チームID」とあるため、TeamIDを入力していたところ、最初はディープリンクが全く動かなくてしばらく悩みました。

注意点(2つ目)

ディープリンクは、アプリがインストールされていない状態でタップされたとしても、インストールしてアプリ起動すると、ディープリンクの情報をアプリが受け取ることができます。 注意が必要なのは、ディープリンクの情報を受け取った後、アプリを再インストールしても、再度ディープリンクの情報を受け取ってしまうことです。 何度も再インストールしても、しばらくは受け取ってしまいます。調べたところ、約1時間は情報が記憶されているようです。 困るのは、ディープリンクのテストを行った後、再インストールを行うと意図せずディープリンクが勝手に動作してしまうことです。 回避方法としては、アプリをアンインストールした後、一度端末の電源をOFFにすることで、ディープリンク情報をリセットすることができます。

注意点(3つ目)

この注意点は、ブログを書き終えてから追記したのですが、念のため、新しいXcodeプロジェクトを作成して試したところ、 application:continueUserActivity:restorationHandler: メソッドが動作しないことが分かりました。

この問題の対処方法を以下に記載します。

原因は、iOS 13以降およびXcode 11以降で生成されるプロジェクトにはSceneDelegateが追加され、これが原因で application:continueUserActivity:restorationHandler: メソッドが呼び出されないようです。

対処方法は2つあります。

1つ目は、SceneDelegateを利用せず削除する方法です。以下を削除してください。

  • AppDelegateの application(_:configurationForConnecting:options:) application(_:didDiscardSceneSessions:)  を削除。
  • info.plist から UIApplicationSceneManifestを削除。
  • SceneDelegate.swift を削除。

2つ目は、SceneDelegateでもディープリンクを受け取れるようにする方法です。

  • SceneDelegateの scene(_:continue:) メソッドを追加し、 以下のように application:continueUserActivity:restorationHandler: メソッドと同じ実装をします。このメソッドでは、アプリのタスクが終了していない状態でディープリンクをタップした時に動作します。
    // アプリのタスクが終了していない状態でディープリンクをタップした時の処理。
    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        guard let userActivity = connectionOptions.userActivities.first(where: { $0.webpageURL != nil }) else { return }
        let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) {
            dynamiclink, error in
            // ここにディープリンクを受け取ったときのコードを書きます。
        }
    }
  • SceneDelegateの scene(_:willConnectTo:options:) メソッドに、userActivityの取得処理を追加し、 application:continueUserActivity:restorationHandler: メソッドと同じ実装をします。 このメソッドでは、アプリのタスクが終了している状態でディープリンクをタップした時に動作します。
    // アプリのタスクが終了している状態でディープリンクをタップした時の処理。
    func scene(_ scene: UIScene,
               willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        guard let userActivity = connectionOptions.userActivities.first(where: { $0.webpageURL != nil }) else { return }
        let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) {
            dynamiclink, error in
            // ここにディープリンクを受け取ったときのコードを書きます。
        }
    }



さいごに

ディープリンクは、アプリのユーザーエクスペリエンスを向上させるうえで非常に有用な機能です。 そして、アプリのインストール数を増やすことにも繋がる機能だと思います。 アプリのユーザーは、特定のコンテンツに簡単かつ直接的にアクセスすることができ、開発者は、効果的なマーケティング戦略を実施できます。

Firebase Dynamic Linksを使えば、このようなディープリンクを簡単かつ迅速に作成することができます。 また、Firebase Dynamic Linksは無料で提供されており、機能も豊富です。 ぜひアプリの成長につながるこの機能を活用してみてください。

お知らせ

このブログを書き終えた数日前に、公式から将来的にサービスの終了が発表されたようですので、利用する際には参考にしてください。

https://firebase.google.com/support/dynamic-links-faq?authuser=0&hl=ja

かなしい

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。