みてね Tech Blog

Firebase Dynamic Links Short Links API -> AppsFlyer OneLink API 移行

に公開

概要

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 実装

server(ruby)

https://dev.appsflyer.com/hc/reference/create-onelink-attribution-link

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

// 参考: 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)
        }
    }
}

テスト方法

まとめ

  • みてねでは、Firebase DynamicLinks で実現していた機能(Deep Link、Deferred Deep Link、短縮URLの生成など)を AppsFlyer OneLink API で代替できました

  • 移行にあたっては、AppsFlyer管理画面でのテンプレート作成と、OneLink生成のAPI実装(サーバー)、アプリ側の Unified Deep Linking 対応(iOS/Android) が必要でした。

  • もし Firebase DynamicLinks の廃止で困っている方がいたら参考にしてもらえると嬉しいです。

みてね Tech Blog
みてね Tech Blog

Discussion