✨
Firebase Dynamic Links Short Links API -> AppsFlyer OneLink API 移行
概要
- 
Firebase DynamicLinks が2024/8/25でサービス終了を受けて家族アルバム みてね(以下、みてね)では Firebase DynamicLinks Short Links API(Short Links API) から AppsFlyer OneLink API(OneLink API)に移行したので実装方法についてまとめます
 - 
Firebase Dynamic Links から AppsFlyer への移行の全般的なことは AppsFlyer にもドキュメントあります
 
DynamicLinks Short Links API で実現していたこと
みてねでは以下の要件を満たすために Short Links API を利用していました
- Deep Link
- URL タップでアプリの指定した画面に飛ばす
 
 - Deferred Deep Link
- アプリ未インストール時は App Store / Google Play Store に遷移してユーザーがインストールしたあとの初回アプリ起動時に指定したDeep Link の画面に飛ばす
 
 - URL をできるだけ短く
- 文字数制限がある SMS 内のリンクとして利用しているため
 
 - 動的な URL を生成する
 
DynamicLinks Short Links API と AppsFlyer OneLink の比較
大体同じことができる印象ですが強いて言えばリンクの有効期限が異なっています
| 項目 | Firebase DynamicLinks Short Links API | AppsFlyer OneLinks API | 
|---|---|---|
| 仕様 | https://firebase.google.com/docs/reference/dynamic-links/link-shortener?hl=ja | https://dev.appsflyer.com/hc/reference/create-onelink-attribution-link | 
| APIエンドポイント | https://firebasedynamiclinks.googleapis.com/v1/shortLinks | 
https://api.appsflyer.com/v1/shortlink/#{onelink-id}(onelink-idはAppsFlyerの管理画面で作成したテンプレートのID)  | 
| 認証方法 | URLパラメーターで key=YOUR_API_KEY を指定 | 
HTTPヘッダー authentication: #{dev_key} を指定 | 
| ベースURL | #{your_subdomain}.page.link | 
#{your_subdomain}.onelink.me | 
| リンク有効期限 | 明示的な期限指定不可。1年以上有効だったという話もあり半永久的に有効かも | デフォルトおよび最大は31日。時/分/日で指定可能 | 
| SDK統合 | Firebase SDK 必須 | AppsFlyer SDK 必須 | 
AppsFlyer 管理画面での作業
- AppsFlyer の管理画面で OneLink テンプレートを作成
 - OneLink テンプレート作成後に表示されるテンプレートIDをこれ以降の server 側の実装で利用します
 
AppsFlyer OneLink 実装
server(ruby)
OneLink 作成
require 'uri'
require 'net/http'
template_id = 'AppsFlyerの管理画面で作成したテンプレートのID'
url = URI("https://onelink.appsflyer.com/shortlink/v1/#{template_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request['accept'] = 'application/json'
request['content-type'] = 'application/json'
request['authorization'] = 'AppsFlyer OneLink API Token を指定'
request.body = {
  data: {
    deep_link_value: 'https://feedme.ca/deeplink', # iOS/Androidアプリ側から取得可能。実際のdeeplinkとするものを指定
    deep_link_sub1: 10, # iOS/Androidアプリ側から取得可能。キーはdeep_link_sub1-10まで指定可能。
    # ------------------------------------------------------
    # これより下のキーはiOS/Androidアプリ側から取得不可。下記リンクにそれぞれの意味合いが詳しく書いてある
    # see: https://support.appsflyer.com/hc/ja/articles/207447163-%E8%A8%88%E6%B8%AC%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%BC
    pid: 'my_media_source_SMS' # 必須。AppsFlyer 管理画面でメディアソースとして集計されるので何か識別できる文字列にする
    af_android_url: 'https://feedme.ca/buybananas', # アプリ未インストールの Android ユーザーを Google Play のアプリページとは異なる URL にリダイレクトしたい時に指定する
    af_ios_url: 'https://feedme.ca/buybananas', # アプリ未インストールの iOS ユーザーを App Store のアプリページとは異なる URL にリダイレクトしたい時に指定する
    is_retargeting: true,
    af_adset: 'my_adset',
    af_channel: 'my_channel',
    af_dp: 'afbasicapp://mainactivity',
    c: 'my_campaign',
  }
}.to_json
response = http.request(request)
# 成功すると https://xxx.onelink.me/template_id/**** のようなonelinkが返ってくる
puts response.read_body
iOS
- 
Associated Domains Entitlement に onelink のドメイン(
xxx.onelink.me)追加 - 
Unified Deep Linking タップした時の処理を実装
 
// 参考: https://github.com/AppsFlyerSDK/appsflyer-onelink-ios-sample-apps/blob/a96399329a369b30263ea4f8cc4558029ea603b3/swift/basic_app/basic_app/AppDelegate.swift#L103
class AppDelegate: UIResponder, UIApplicationDelegate,
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        AppsFlyerLib.shared().deepLinkDelegate = self
    }
}
extension AppDelegate: DeepLinkDelegate {
    func didResolveDeepLink(_ result: DeepLinkResult) {
        switch result.status {
        case .notFound:
            print("Deep link not found")
        case .found:
            guard let deepLinkStr = result.deepLink?.toString() else { return }
            print("DeepLink data is: \(deepLinkStr)")
            if( result.deepLink?.isDeferred == true) {
                print("This is a deferred deep link")
            } else {
                print("This is a direct deep link")
            }
            // deepLinkString (server 側で指定した deep_link_value) をごとの処理を行う
        case .failure:
            print("Error \(result.error?.localizedDescription ?? "")")
        }
    }
}
Android
- 
AndroidManifest.xmlに OneLink のドメイン(xxx.onelink.me)の intent-filter を追加 
        <activity
            android:name=".SampleActivity"
            android:theme="@style/xxxx" />
            <!-- 基本的には DynamicLinks の intent-filter と同じところに書くことになると思われる -->
            <!-- appsflyer OneLink -->
            <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" />
                <data
                    android:host="${one_link_domain}" 
                    android:scheme="https" />
            </intent-filter>
- Unified Deep Linking タップした時の処理を実装
 
// 参考: https://github.com/AppsFlyerSDK/appsflyer-onelink-android-sample-apps/blob/master/java/basic_app/app/src/main/java/com/appsflyer/onelink/appsflyeronelinkbasicapp/AppsflyerBasicApp.java#L31-L70
class AppsflyerBasicApp : Application {
    override fun onCreate() {
        val conversionListener = object : AppsFlyerConversionListener {
            override fun onConversionDataSuccess(data: MutableMap<String, Any>?) {}
            override fun onConversionDataFail(error: String?) {}
            override fun onAppOpenAttribution(data: MutableMap<String, String>?) {}
            override fun onAttributionFailure(error: String?) {}
        }
        AppsFlyerLib.getInstance().init(AF_DEV_KEY, conversionListener, this).apply {
            subscribeForDeepLink {
                val deepLinkValue = it.deepLink.deepLinkValue ?: return@subscribeForDeepLink
                if (it.deepLink.isDeferred()) {
                    Log.d(LOG_TAG, "This is a deferred deep link")
                } else {
                    Log.d(LOG_TAG, "This is a direct deep link")
                }
                // deepLinkValue (server 側で指定した deep_link_value) をごとの処理を行う
            }
            start(this@AppsflyerBasicApp)
        }
    }
}
テスト方法
- iOS / Android アプリをテストするにはテストデバイスとしてAppsFlyerの管理画面に登録する必要があります
- https://support.appsflyer.com/hc/ja/articles/207031996-テストデバイスの登録
 - Deferred Deep Link は👆を登録しないと動作しませんでした
 
 
まとめ
- 
みてねでは、Firebase DynamicLinks で実現していた機能(Deep Link、Deferred Deep Link、短縮URLの生成など)を AppsFlyer OneLink API で代替できました
 - 
移行にあたっては、AppsFlyer管理画面でのテンプレート作成と、OneLink生成のAPI実装(サーバー)、アプリ側の Unified Deep Linking 対応(iOS/Android) が必要でした。
 - 
もし Firebase DynamicLinks の廃止で困っている方がいたら参考にしてもらえると嬉しいです。
 
Discussion