딥링킹 지원 추가
딥링크는 사용자를 앱 내의 특정 콘텐츠로 연결합니다. 사용자가 앱이 설치된 기기에서 딥링크를 탭하면 앱이 제품 페이지나 특정 경험 등 의도한 콘텐츠로 바로 연결됩니다.
Singular 추적 링크는 표준 딥링킹(설치된 앱의 경우)과 디퍼드 딥링킹(신규 설치의 경우)을 모두 지원합니다. 자세한 내용은 딥링킹 FAQ 및 Singular 링크 FAQ를 참조하세요.
요구 사항
전제 조건
앱에 딥링킹을 사용하려면 Singular 링크 사전 요구 사항을 완료하세요.
참고:
- 이 문서에서는 귀하의 조직이 Singular의 추적 링크 기술인 Singular 링크를 사용하고 있다고 가정합니다. 기존 고객은 레거시 트래킹 링크를 사용하고 있을 수 있습니다.
- 앱의 딥링크 대상은 Singular의 앱 페이지에서 구성해야 합니다( 어트리뷰션 추적을 위한 앱 구성하기 참조).
사용 가능한 파라미터
Singular링크 핸들러는 앱이 열릴 때 Singular 트래킹 링크의 딥링크, 디퍼드 딥링크, 패스스루 파라미터에 대한 액세스를 제공합니다.
- 딥링크(_dl): 링크를 클릭하는 사용자의 앱 내 목적지 URL
- 디퍼드 딥링크(_ddl): 링크를 클릭한 후 앱을 설치하는 사용자를 위한 목적지 URL
- 패스스루(_p): 추가 컨텍스트를 위해 추적 링크를 통해 전달되는 사용자 지정 데이터
플랫폼 구성
iOS 플랫폼 구성
iOS 앱디렉티브 업데이트
launchOptions 및 userActivity 개체를 AppDelegate 파일에서 Singular SDK에 전달하여 Singular SDK가 실행 관련 데이터를 처리하고 딥링크를 처리할 수 있도록 설정합니다.
이러한 개체에는 앱이 실행된 방법과 이유에 대한 중요한 정보가 포함되어 있으며, Singular는 어트리뷰션 추적 및 딥링크 탐색에 사용합니다.
Objective-C 구현
// Top of AppDelegate.m
#import "SingularAppDelegate.h"
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
[SingularAppDelegate shared].launchOptions = launchOptions;
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>
*restorableObjects))restorationHandler {
[[SingularAppDelegate shared] continueUserActivity:userActivity
restorationHandler:restorationHandler];
return [super application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
[[SingularAppDelegate shared] handleOpenUrl:url options:options];
return [super application:app openURL:url options:options];
}
신속한 구현
import singular_flutter_sdk
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.launchOptions = launchOptions
}
return super.application(application,
didFinishLaunchingWithOptions:launchOptions)
}
override func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.continueUserActivity(userActivity,
restorationHandler: nil)
}
return super.application(application,
continue: userActivity,
restorationHandler: restorationHandler)
}
override func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let singularAppDelegate = SingularAppDelegate.shared() {
singularAppDelegate.handleOpen(url, options: options)
}
return super.application(app, open: url, options: options)
}
안드로이드 플랫폼 구성
안드로이드 메인 액티비티 업데이트
MainActivity 파일을 수정하여 Intent 오브젝트를 Singular SDK에 전달함으로써 Singular SDK가 실행 관련 데이터를 처리하고 딥링크를 처리할 수 있도록 합니다.
Intent 객체에는 앱이 실행된 방법과 이유에 대한 정보가 포함되어 있으며, Singular는 어트리뷰션 추적 및 딥링크 탐색에 사용합니다.
Java 구현
// Add as part of the imports at the top of the class
import android.content.Intent;
import com.singular.flutter_sdk.SingularBridge;
// Add to the MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridge.onNewIntent(intent);
}
}
Kotlin 구현
// Add as part of the imports at the top of the class
import android.content.Intent
import com.singular.flutter_sdk.SingularBridge
// Add to the MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridge.onNewIntent(intent)
}
}
SDK 구성
Singular 링크 핸들러 구현
SDK 초기화 중에 들어오는 딥링크 및 디퍼드된 딥링크 데이터를 처리하도록 singularLinksHandler 콜백을 구성합니다.
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
initializeSDK();
}
void initializeSDK() {
// Create SDK configuration
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
// Configure deep link handler
config.singularLinksHandler = (SingularLinkParams params) {
print('=== Singular Link Resolved ===');
print('Deep link: ${params.deeplink}');
print('Passthrough: ${params.passthrough}');
print('Is deferred: ${params.isDeferred}');
// Handle deep link navigation
if (params.deeplink != null) {
handleDeepLink(params.deeplink!, params.isDeferred);
}
// Handle passthrough data
if (params.passthrough != null) {
handlePassthroughData(params.passthrough!);
}
};
// Initialize SDK
Singular.start(config);
}
void handleDeepLink(String url, bool isDeferred) {
print('Routing to: $url (Deferred: $isDeferred)');
// Parse URL and navigate to appropriate screen
// Example: myapp://product/123
if (url.contains('product')) {
final productId = url.split('/').last;
navigateToProduct(productId);
} else if (url.contains('promo')) {
navigateToPromo(url);
}
}
void handlePassthroughData(String passthrough) {
print('Processing passthrough data: $passthrough');
// Parse and use passthrough data as needed
}
void navigateToProduct(String productId) {
// Your navigation logic
print('Navigating to product: $productId');
}
void navigateToPromo(String url) {
// Your navigation logic
print('Navigating to promo: $url');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
구성 속성:
void Function(SingularLinkParams)? singularLinksHandler
참고: singularLinksHandler 콜백은 앱이 Singular 링크를 통해 열릴 때만 트리거됩니다. 자세한 내용은 Singular 링크 FAQ를 참조하세요.
전체 문서는 Singular링크 핸들러 참조를 참조하세요.
핸들러 동작
링크 해상도 이해
singularLinksHandler 은 앱이 새로 설치되었는지 또는 이미 설치되었는지에 따라 다르게 동작합니다.
새로 설치(디퍼드 딥링크)
새로 설치한 경우 앱이 실행될 때 열기 URL이 존재하지 않습니다. Singular는 추적 링크에 딥링크 또는 디퍼드 딥링크 값이 포함되어 있는지 확인하기 위해 어트리뷰션을 완료합니다.
디퍼드 딥링크 흐름:
- 사용자가 딥링크 값으로 구성된 Singular 트래킹 링크를 클릭합니다.
- 사용자가 앱을 처음 설치하고 실행합니다.
- Singular SDK가 첫 번째 세션을 Singular 서버로 전송합니다.
- 어트리뷰션이 트래킹 링크에서 딥링크를 완성하고 식별합니다.
- 딥링크 값이
deeplink파라미터의singularLinksHandler콜백에isDeferred = true로 반환됩니다.
디퍼드 딥링크 테스트:
- 테스트 기기에서 앱을 제거합니다(현재 앱이 설치되어 있는 경우).
- iOS: IDFA를 재설정합니다. Android: Google 광고 ID(GAID)를 재설정합니다.
- 기기에서 Singular 추적 링크를 클릭합니다(딥링크 값으로 구성되었는지 확인).
- 앱을 설치하고 엽니다.
어트리뷰션이 성공적으로 완료되고 디퍼드 딥링크 값이 singularLinksHandler 핸들러로 전달됩니다.
프로 팁: 다른 패키지 이름(예: com.example.prod 대신 com.example.dev )을 사용하는 개발 빌드로 딥링크를 테스트하는 경우, 개발 앱의 패키지 이름에 맞게 추적 링크를 구성하세요. 테스트 링크를 클릭한 후 앱 스토어에서 프로덕션 앱을 다운로드하지 않고 Android Studio 또는 Xcode를 통해 기기에 직접 개발 빌드를 설치합니다.
이미 설치됨(즉시 딥 링크)
앱이 이미 설치되어 있는 경우 Singular 링크를 클릭하면 유니버설 링크(iOS) 또는 Android 앱 링크 기술을 사용하여 앱이 즉시 열립니다.
즉시 딥링크 흐름:
- 사용자가 Singular 추적 링크를 클릭합니다.
- 운영 체제는 전체 Singular 추적 링크가 포함된 오픈 URL을 제공합니다.
- SDK 초기화 중에 Singular가 URL을 구문 분석합니다.
- Singular가
deeplink및passthrough값을 추출합니다. - 값은
singularLinksHandler핸들러를 통해isDeferred = false으로 반환됩니다.
고급 기능
패스스루 매개변수
패스스루 매개변수를 사용하여 추적 링크 클릭에서 추가 데이터를 캡처합니다.
추적 링크에 passthrough (_p) 파라미터가 포함된 경우 singularLinksHandler 핸들러의 passthrough 파라미터에 해당 데이터가 포함됩니다. 이를 사용하여 캠페인 메타데이터, 사용자 세분화 데이터 또는 앱에서 필요한 사용자 지정 정보를 캡처할 수 있습니다.
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
import 'dart:convert';
SingularConfig config = SingularConfig('API_KEY', 'SECRET');
config.singularLinksHandler = (SingularLinkParams params) {
// Extract passthrough data
final passthroughData = params.passthrough;
if (passthroughData != null) {
try {
// Parse JSON passthrough data
final jsonData = jsonDecode(passthroughData);
print('Campaign ID: ${jsonData['campaign_id']}');
print('User Segment: ${jsonData['segment']}');
print('Promo Code: ${jsonData['promo_code']}');
// Apply campaign-specific settings
applyCampaignSettings(jsonData);
} catch (error) {
print('Error parsing passthrough data: $error');
}
}
};
void applyCampaignSettings(Map<String, dynamic> data) {
// Your campaign logic here
print('Applying campaign settings: $data');
}
모든 쿼리 파라미터 전달
추적 링크에 _forward_params=2 파라미터를 추가하여 추적 링크 URL에서 모든 쿼리 파라미터를 캡처합니다.
_forward_params=2 을 추적 링크에 추가하면 모든 쿼리 파라미터가 singularLinksHandler 핸들러의 deeplink 파라미터에 포함되므로 모든 파라미터가 포함된 전체 URL에 액세스할 수 있습니다.
추적 링크 예시:https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_forward_params=2&utm_source=facebook&promo=SALE2024
singularLinksHandler 핸들러가 수신합니다:deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"
전체 구현 예시
Flutter 앱의 탐색, 패스스루 처리 및 매개변수 전달을 통한 포괄적인 딥링킹 구현.
import 'package:flutter/material.dart';
import 'package:singular_flutter_sdk/singular.dart';
import 'package:singular_flutter_sdk/singular_config.dart';
import 'package:singular_flutter_sdk/singular_link_params.dart';
import 'dart:convert';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
void initState() {
super.initState();
initializeSDK();
}
void initializeSDK() {
SingularConfig config = SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
);
config.singularLinksHandler = (SingularLinkParams params) {
print('=== Singular Link Resolved ===');
print('Deep link: ${params.deeplink}');
print('Passthrough: ${params.passthrough}');
print('Is deferred: ${params.isDeferred}');
// Handle passthrough data
if (params.passthrough != null) {
handlePassthroughData(params.passthrough!);
}
// Handle deep link navigation
if (params.deeplink != null) {
handleDeepLinkNavigation(params.deeplink!, params.isDeferred);
}
};
Singular.start(config);
}
void handlePassthroughData(String passthroughString) {
try {
final data = jsonDecode(passthroughString);
// Apply promo code if present
if (data.containsKey('promo_code')) {
applyPromoCode(data['promo_code']);
}
// Set user segment
if (data.containsKey('segment')) {
setUserSegment(data['segment']);
}
// Track campaign
if (data.containsKey('campaign_id')) {
trackCampaign(data['campaign_id']);
}
} catch (error) {
print('Error parsing passthrough: $error');
}
}
void handleDeepLinkNavigation(String url, bool isDeferred) {
// Parse URL to extract route and parameters
final urlObj = parseDeepLink(url);
print('Navigating to: ${urlObj['route']}');
print('Parameters: ${urlObj['params']}');
print('Deferred install: $isDeferred');
// Route based on deep link structure
switch (urlObj['route']) {
case 'product':
navigateToProduct(urlObj['params']['id']);
break;
case 'promo':
navigateToPromo(urlObj['params']['code']);
break;
case 'category':
navigateToCategory(urlObj['params']['name']);
break;
default:
navigateToHome();
}
}
Map<String, dynamic> parseDeepLink(String url) {
// Parse myapp://product/123?variant=blue
final parts = url.split('?');
final path = parts[0];
final queryString = parts.length > 1 ? parts[1] : null;
// Extract path components
final pathParts = path.replaceFirst(RegExp(r'^[^:]+://'), '').split('/');
final route = pathParts[0];
// Parse parameters
final params = <String, String>{};
// Add path parameters
if (pathParts.length > 1) {
params['id'] = pathParts[1];
}
// Add query parameters
if (queryString != null) {
queryString.split('&').forEach((pair) {
final keyValue = pair.split('=');
if (keyValue.length == 2) {
params[keyValue[0]] = Uri.decodeComponent(keyValue[1]);
}
});
}
return {
'route': route,
'params': params
};
}
// Navigation functions
void navigateToProduct(String? productId) {
if (productId != null) {
print('Navigating to product: $productId');
// Use your navigation framework (Navigator, GoRouter, etc.)
navigatorKey.currentState?.pushNamed('/product/$productId');
}
}
void navigateToPromo(String? promoCode) {
if (promoCode != null) {
print('Navigating to promo: $promoCode');
navigatorKey.currentState?.pushNamed('/promo/$promoCode');
}
}
void navigateToCategory(String? categoryName) {
if (categoryName != null) {
print('Navigating to category: $categoryName');
navigatorKey.currentState?.pushNamed('/category/$categoryName');
}
}
void navigateToHome() {
print('Navigating to home');
navigatorKey.currentState?.pushNamed('/');
}
// Utility functions
void applyPromoCode(String code) {
print('Applying promo code: $code');
// Your promo code logic
}
void setUserSegment(String segment) {
print('Setting user segment: $segment');
// Your user segmentation logic
}
void trackCampaign(String campaignId) {
print('Tracking campaign: $campaignId');
// Your campaign tracking logic
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
navigatorKey: navigatorKey,
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/product': (context) => ProductScreen(),
'/promo': (context) => PromoScreen(),
'/category': (context) => CategoryScreen(),
},
);
}
}
// Placeholder screen classes
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(child: Text('Home Screen')),
);
}
}
class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product')),
body: Center(child: Text('Product Screen')),
);
}
}
class PromoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Promo')),
body: Center(child: Text('Promo Screen')),
);
}
}
class CategoryScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Category')),
body: Center(child: Text('Category Screen')),
);
}
}
모범 사례:
- URL을 안전하게 파싱하기: 보안 취약점을 방지하기 위해 내비게이션 전에 항상 딥링크 URL의 유효성을 검사하고 위생 처리합니다.
- 탐색 상태 처리: 탐색 상태 처리: MaterialApp이 완전히 초기화되기 전에 탐색을 활성화하려면 탐색 상태용 GlobalKey를 사용합니다.
- 두 시나리오를 모두 테스트합니다: 개발 중에 즉시 딥링크(앱 설치)와 디퍼드 딥링크(새로 설치)를 모두 테스트합니다.
- 디버깅을 위한 로그: 개발 중 포괄적인 로깅을 활성화하여 딥링크 해상도 및 탐색 흐름을 추적합니다.
- 오류 처리: JSON 구문 분석 및 탐색 작업을 위한 try-catch 블록을 구현하여 잘못된 데이터를 정상적으로 처리합니다.