Flutter 實作 DeepLink 完整指南 ⎮ Part 4: 適配與掌握社交平台
不可忽視的 Deep Link 體驗
如果帳戶不是 Medium Member 的朋友,可以點擊我的開放連結瀏覽文章。
社交平台的適配#
產品使用上經常會有個情境,邀請連結或商品連結需要分享給朋友,這時候的管道通常會是 Line、Facebook、IG 等等,經由聊天室轉發,會有幾個期望情境或流程,不過有時候現實運作卻不一樣。以 IG、Facebook 來說不同系統的手機就有不同效果,使用 Http Link 的 Android App Link、iOS Universal Link,可能會遇到幾個場景
- Android 可以開啟連結並開啟 APP 進行操作,但是 iOS 就不同了,會需要有一個中繼頁面,讓用戶點擊按鈕後打開 DeepLink 連結才能正常,否則在 IG 中會不被辨識,點擊後會閃一下然後被擋掉
- 不同手機系統,有些連結能正常運作、跳轉,有些連結卻只會開啟商店
期望:
- 沒有安裝的用戶,跳轉到商店引導下載
- 有下載且登入的用戶,可以開啟 App 並進入到指定的商品頁面,甚至是直接帶入或顯示相關資訊
原因:在每個社交產品的運作上,開啟網頁、連結都會有自己定義的行為,可能會需要在 URI 上添加一些特定參數,給平台進行驗證並協助開啟。例如:Line 上需要添加 ?openExternalBrowser=1
iOS#
Instagram#
- iOS 裝置在 IG 無法直接點擊連結就開啟 App,DeepLink 會被 IG 在內部瀏覽器擋掉
- 必須先用 In-App Browser 開啟一個中繼網頁,上面點擊 DeepLink 按鈕打開實際網址,Deep Link 才能有效地開啟 App,這時候才可以正常處理連結,並跳轉到指定頁面。(例如:可以用 LinkTree 當成中繼頁面)
Line#
- In-App Browser 都會讓 DeepLink 失效,除非在 query 添加
openExternalBrowser=1
https://hello.yiichen.com/product/xxx?openExternalBrowser=1
Android#
Instagram#
- 可以直接點擊 DeepLink 就開啟 app,但如果打開中繼頁面再點擊 DeepLink 跳轉,這樣會失效,即使已經下載了 App 仍然會跳轉到 Play Store商店
- 用戶必須事先開過 App 在背景運行,如果沒有先開啟,經過 Deep link 重新啟動 APP 的情況, 不會收到 DeepLink 相關資訊,也就只能到
/Route path
以上問題可以透過三方服務來協助我們,例如: AppsFlyer、Branch。使用 Custom Scheme 的連結可以正常運作,幫我們開啟對應的 APP
第三方服務的優勢#
- Useful when you don’t have a domain 沒有網域
- Fallback mechanisms 觸發機制
- Link shortening 短連結
- Analytics 數據分析
- Attribution tracking 資料追蹤
- Deferred deep linking 延遲深度鏈接:針對剛安裝應用的用戶,確保來源並跳轉到指定頁面或是蒐集相關數據
AppsFlyer#
Setup (Android)#
設定 AndroidManifest.xml,vibz-app.onelink.me domain 為 https,也就是用戶點擊時的公開連結,需要透過它開啟 APP,並指向到我們內部設定的自定義 URI
<!-- This link is from AppsFlyer. It should be added. -->
<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:scheme="https"
android:host="vibz-app.onelink.me"
android:pathPrefix="/PqsT" />
</intent-filter>
自定義的 URI 設置,實際最終跳轉的通道
<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:scheme="vibz"
android:host="vibz.cool" />
</intent-filter>
Setup (iOS)#
AppsFlyer Console Setting#
避免使用 http、https 開頭的 scheme,很常會在社群平台被擋下或是影響跳轉,例如:IG 點擊連結後會跳轉到 404 網頁。可以為品牌定義自己的 scheme 來使用。
設置 https scheme 會無法開啟商店或是 APP
幫品牌定義了自己的 scheme,在 IG 點擊連結後就能正常開啟 OneLink 的引導網頁,點擊後即可開啟 APP 商店。
Smart Using AppsFlyer#
重復使用品牌的 AppsFlyer 格式但不生成新的子連結 ID,使用一對多的方式,可以更改指定參數來完成,不需要為每個行為都生成一個新的 OneLink。因此可避免創建的 API 使用流量,通常可能會想到透過後端去生成所有連結,但每產一個都是成本,依照方案有不同限制。而使用自定義參數去動態配置便能解決此問題。
通常格式都會是品牌名稱 vibz 加上 .onelink.me,後面為 Templaste ID,最後面可以加上獨立 ID,但這裡自定義我只需要 Template 辨識就好。
https://vibz-app.onelink.me/PqsT
af_dp→ fallback 連結,真正 APP 有設定的 Link 格式與內容,也是主要需要修改的部分af_force_deeplink→ iOS 需要設置的參數,強制執行 DeepLink 互動,否則有可能在有安裝 APP 的情境下還是開啟商店,設置為 true
// Normal
<https://vibz-app.onelink.me/PqsT?af_dp=vibz://share.vibz.cool/campaigns/dthm31/claim?claim_code=bmljZQ==&af_force_deeplink=true>
// Wrap own short-link
<https://link.vibz.cool/link/gXhY1CKsS>
AppsFlyer API Access#
也可以直接請求 AppsFlyer API 生成 AppsFlyer 連結,也是一個選項,根據實際需求進行選擇與實作
{
"af_ad": "yellow_bananas",
"af_adset": "my_adset",
"af_android_url": "<https://feedme.ca/buybananas>",
"af_channel": "my_channel",
"af_dp": "afbasicapp://mainactivity",
"af_ios_url": "<https://feedme.ca/buybananas>",
"c": "my_campaign",
"deep_link_value": "bananas",
"deep_link_sub1": 10,
"is_retargeting": true,
"pid": "my_media_source_SMS"
}
Flutter Development#
Handle All Links#
可統一使用 onDeepLinking(),捕捉所有情境:
- 使用者點擊 OneLink 短 URL。
- iOS 通用連結/ Android 應用程式連結(用於深度連結)或延遲的深度連結會觸發 SDK。
- SDK觸發
didResolveDeepLink方法,將深度連結結果物件傳遞給使用者。 onDeepLinking方法使用包含deep_link_value和其他參數的深層連結結果物件來為使用者建立個人化體驗,這是 OneLink 的主要目標。
appsflyerSdk.onDeepLinking((DeepLinkResult dp) {
switch (dp.status) {
case Status.FOUND:
debugPrint(dp.deepLink.toString());
break;
case Status.NOT_FOUND:
debugPrint("deep link not found");
break;
case Status.ERROR:
debugPrint("deep link error: ${dp.error}");
break;
case Status.PARSE_ERROR:
debugPrint("deep link status parsing error");
break;
}
}
// dp.deepLink
{
"click_http_referrer": "",
"af_sub1": "",
"af_sub2": "",
"af_sub3": "",
"af_sub4": "",
"af_sub5": "",
"deep_link_value": "vibz://share.vibz.cool/campaigns/nhdwc_1st/claim",
"campaign": "",
"campaign_id": "",
"match_type": "probabilistic",
"media_source": "",
"is_deferred": true
}
由 DeepLink 類別儲存連結相關資訊:
class DeepLink {
DeepLink(this._clickEvent);
final Map<String , dynamic> _clickEvent;
Map<String , dynamic> get clickEvent => _clickEvent;
String? get deepLinkValue => _clickEvent["deep_link_value"] as String;
String? get matchType => _clickEvent["match_type"] as String;
String? get clickHttpReferrer => _clickEvent["click_http_referrer"] as String;
String? get mediaSource => _clickEvent["media_source"] as String;
String? get campaign => _clickEvent["campaign"] as String;
String? get campaignId => _clickEvent["campaign_id"] as String;
String? get afSub1 => _clickEvent["af_sub1"] as String;
String? get afSub2 => _clickEvent["af_sub2"] as String;
String? get afSub3 => _clickEvent["af_sub3"] as String;
String? get afSub4 => _clickEvent["af_sub4"] as String;
String? get afSub5 => _clickEvent["af_sub5"] as String;
bool get isDeferred => _clickEvent["is_deferred"] as bool;
@override
String toString() {
return 'DeepLink: ${jsonEncode(_clickEvent)}';
}
String? getStringValue(String key) {
return _clickEvent[key] as String;
}
}
提醒:如果有在屬性
deep_link_value或其他欄位放入 Url 資料,通常&在 HTML 編碼為&,如果要直接使用的話需要清理一下。可以簡單透過 String replace api 處理
// For String
final cleanUri = link.replaceAll('amp;', '');
// For Uri
final cleanUri = uri?.replace(query: uri.query.replaceAll('amp;', ''));
Deferred Deep Linking#
如果應用未安裝怎麼辦?這是使用延遲深度連結的地方。當應用程式未安裝時,點擊連結會將使用者引導至商店下載應用程式。延遲深層連結將深層連結過程推遲或延遲到應用程式下載後,並確保用戶安裝後到達應用程式中的正確位置。
使用 onInstallConversionData() 取得安裝應用後的狀態,包含一些基礎資訊。
appsflyerSdk.onInstallConversionData((res) {
debugPrint('DeepLink - AppsFlyer - onInstallConversionData - $res');
});
可惜的是我們目前只能抓到幾種資料:
- is_first_launch: 是否第一次開啟。代表安裝後的第一次啟動
- install_time: 安裝時間
{
"status": "success",
"payload": {
"is_first_launch": true,
"install_time": "2024-06-02 15:33:20.791",
"af_message": "organic install",
"af_status": "Organic"
}
}
沒有先前在連結提供的自定義資訊,因此我們無法精準定位到使用者的意圖。
Deep Link for Social 完整實作#
可以直接使用 Appsflyer 或平台提供的 Deep Link 連結,但如果想要有品牌自定義的 URI,可自行包裝,進行連結跳轉。
以下使用我的專案範例作為講解:
1️⃣ 原始 Custom Scheme Link
vibz://share.vibz.cool/campaigns/vibz-drop-42/claim
2️⃣ 精緻包裝的 AppsFlyer 連結
https://vibz-link.onelink.me/0Smm?af_og_title=現在參與活動來贏得 回訊息啦 VIBE 風格徽章 — VIBZ&af_og_description=在 VIBZ 上參與限時活動來蒐集 回訊息啦 VIBE 風格徽章!&af_og_image=https://production-vibz-core-campaignimagebucketaa7fff6e-v5kjcb897wfy.s3.ap-northeast-1.amazonaws.com%2Fvibz-drop-42.png&af\_param\_forwarding=true&af\_force\_deeplink=true&af\_dp=vibz://share.vibz.cool/campaigns/vibz-drop-42/claim&deep\_link\_value=vibz://share.vibz.cool/campaigns/vibz-drop-42/claim&af\_web\_dp=https://www.vibz.cool
3️⃣ 公開分享連結
https://link.vibz.cool/link/86XhztgjJ
參數資料:
af_og_title→ 連結顯示標題af_og_description→ 連結描述af_og_image→ 連結圖像af_deeplink→ trueaf_param_forwarding→ trueaf_force_deeplink→ 允許 DeepLink 運作,針對一些內部瀏覽器。trueaf_dp→ 實際 DeepLink 溝通連結。APP 端使用 Custom Schemedeep_link_value→ 實際 DeepLink 溝通連結。APP 端使用 Custom Schemedeep_link_sub1→ 自定義資訊af_web_dp→ 網頁連結,可是制為官方連結
實作細節#
實作過後,得出 Deep Link 完整情境的掌握需要 AppLinks 和 AppsFlyer 協作
- AppLinks 使用
getInitialAppLink()捕捉 App 完全關閉(terminated)時觸發的 Link - AppsFlyer 負責處理一般情境下的 Link,尤其是處理在第三方社交平台的操作,主要針對 Custom Scheme 連結,當 APP 在前景或背景都能捕捉
- APP 內只使用了自己的 Custom Scheme,沒有 Http Scheme
Conclusion#
在本文中我們說明 Deep Link 與社交平台的互動細節,透過不同的實作方式讓連結能完整的被 APP 掌握,在裝置的各種運行情境下都能正常運作,最終協助用戶解決問題,有效提升對應用的好感度。
有關 Deep Link 系列的文章就到這裡告一段落了,如果想熟悉每個環節的話,可以點擊以下的其他連結:
- Flutter 實作 DeepLink 完整指南 ⎮ Part 1: 基本介紹
- Flutter 實作 DeepLink 完整指南 ⎮ Part 2: Android 與 iOS 設定
- Flutter 實作 DeepLink 完整指南 ⎮ Part 3: Flutter 開發
當然文章主要是希望給予讀者一點啟發,不需要拘泥於作法。非常期待大家的踩坑分享與開發心得!
Other Articles#
- Flutter September 2024 💙 Flutter Monthly
- Flutter August 2024 💙 Flutter Monthly
- Flutter June 2024 💙 Flutter Monthly
- What can I do with “Extension Types” in Dart?
- Flutter May 2024 💙 Flutter Monthly
- Flutter April 2024 💙 Flutter Monthly
- Flutter March 2024 💙 Flutter Monthly
- Flutter February 2024 💙 Flutter Monthly
- Flutter 3.19 & Dart 3.3 改版重點!
- Flutter January 2024 💙 Flutter Monthly
- Use Dart 3 to Improve Development Skills. More Examples and Tips.
- Flutter December 2023 💙 Flutter Monthly
- Flutter November 2023 💙 Flutter Monthly
- Get Familiar with Dart 3, Make your Life Easier!
- Flutter 3.16 & Dart 3.2 重點整理來了!
- Flutter October 2023 💙 Flutter Monthly
- Flutter September 2023 💙 Flutter Monthly
- 添加預覽影片到 App Store,提升品牌形象
- Fluttercon 2023 技術研討會
- Wow! Flutter runs on Apple Vision Pro!
- 這次 Flutter 3.10 與 Dart 3 又強大了多少?Google IO 告訴你
- Flutter Meetup #1 聚會有什麼?還有 Flutter 四月大小事!
- 提升開發效率的好物,Mason 讓你輕鬆撰寫自定義模板!
- 教你製作強大的 Rive 動畫,完成一隻 Flutter Dash,在 APP 跟它互動!
- Flutter 如何根據 Flavor 多環境載入對應的 Firebase Config
- Isolates 在 Flutter 3.7 & Dart 2.19 的升級,你該知道一下!
- 讓人驚艷的 Flutter Forward,釋出 Flutter 3.7 和 Dart 2.19
- 學會運用 Flutter Widgetbook,該管好自己和公司的元件庫了!
本文原刊登於 Medium。