딥링킹 지원 추가
딥링크는 사용자를 앱 내의 특정 콘텐츠로 연결합니다. 사용자가 앱이 설치된 기기에서 딥링크를 탭하면 앱이 제품 페이지나 특정 경험 등 의도한 콘텐츠로 바로 연결됩니다.
Singular 추적 링크는 표준 딥링킹(설치된 앱의 경우)과 디퍼드 딥링킹(신규 설치의 경우)을 모두 지원합니다. 자세한 내용은 딥링킹 FAQ 및 Singular 링크 FAQ를 참조하세요.
구현 경로: 이 가이드는 베어 리액트 네이티브와 엑스포 구현을 모두 다룹니다. 엑스포는 네이티브 코드 관리를 추상화하여 React Native 개발을 간소화하는 반면, 베어 리액트 네이티브는 네이티브 iOS 및 안드로이드 폴더에 직접 액세스할 수 있기 때문에 차이가 있습니다. 프로젝트 구조에 적합한 구현 경로를 선택하세요.
요구 사항
전제 조건
앱에 딥링킹을 사용하려면 Singular 링크 사전 요구 사항을 완료하세요.
참고:
- 이 문서에서는 조직에서 Singular의 추적 링크 기술인 Singular 링크를 사용하고 있다고 가정합니다. 기존 고객은 레거시 트래킹 링크를 사용하고 있을 수 있습니다.
- 앱의 딥링크 대상은 Singular의 앱 페이지에서 구성해야 합니다( 어트리뷰션 추적을 위한 앱 구성하기 참조).
Singular 링크 핸들러 구현
Singular링크 핸들러는 앱이 열릴 때 Singular 추적 링크에서 딥링크, 디퍼드 딥링크, 패스스루 매개변수 및 URL 매개변수를 검색하는 콜백 메커니즘을 제공합니다.
사용 가능한 파라미터
- 딥링크(_dl): 링크를 클릭하는 사용자의 앱 내 목적지 URL입니다.
- 디퍼드 딥링크(_ddl): 링크를 클릭한 후 앱을 설치하는 사용자를 위한 목적지 URL
- 패스스루(_p): 추가 컨텍스트를 위해 추적 링크를 통해 전달된 사용자 지정 데이터
- urlParameters: 앱을 연 Singular 링크의 쿼리 파라미터 객체. 디퍼드 딥링크 시나리오에는 적용되지 않습니다.
베어 리액트 네이티브 구현
네이티브 iOS 및 Android 코드에 직접 액세스하여 베어 React Native 프로젝트에 대한 딥링킹을 구성합니다.
iOS 플랫폼 구성
iOS 앱 델리게이트 업데이트
launchOptions 및 userActivity 객체를 AppDelegate 파일에서 Singular SDK에 전달하여 Singular SDK가 실행 관련 데이터를 처리하고 딥링크를 처리하도록 설정합니다.
import Singular
// Deferred Deep link and Short Link support
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithLaunchOptions:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: launchOptions, afterDelay: 1)
}
}
factory.startReactNative(
withModuleName: "AppName", // Update with your App Name
in: window,
launchOptions: launchOptions
)
return true
}
// Universal Links (NSUserActivity)
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let url = userActivity.webpageURL {
print("Singular: Universal link received:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
// Top of the AppDelegate.mm
#import <React/RCTLinkingManager.h>
#import <Singular-React-Native/SingularBridge.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add inside didFinishLaunchingWithOptions
[SingularBridge startSessionWithLaunchOptions:launchOptions];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
[SingularBridge startSessionWithUserActivity:userActivity];
return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}
이러한 방법을 사용하면 Singular SDK가 앱이 실행된 방법과 이유에 대한 중요한 정보를 수신하여 어트리뷰션 추적 및 딥링크 탐색에 사용할 수 있습니다.
안드로이드 플랫폼 구성
안드로이드 메인 액티비티 업데이트
MainActivity 파일을 수정하여 Intent 오브젝트를 Singular SDK에 전달함으로써 Singular SDK가 실행 관련 데이터를 처리하고 딥링크를 처리할 수 있도록 합니다.
// Add as part of the imports at the top of the class
import android.content.Intent
import net.singular.react_native.SingularBridgeModule
// Add to the MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}
// Add as part of the imports at the top of the class
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;
// Add to the MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}
Intent 개체에는 앱이 실행된 방법과 이유에 대한 정보가 포함되어 있으며, Singular는 어트리뷰션 추적 및 딥링크 탐색에 사용합니다.
SDK 구성
Singular링크 콜백 추가
SDK 초기화 중에 들어오는 딥링크 및 디퍼드 딥링크 데이터를 처리하도록 withSingularLink 콜백을 구성합니다.
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/jsNativeSingular';
import { NativeEventEmitter } from 'react-native';
function App() {
useEffect(() => {
// Create config
const config: SingularConfig = {
apikey: 'YOUR_SDK_KEY',
secret: 'YOUR_SDK_SECRET'
// Note: Deep link handlers are NOT set in config for TurboModule
};
NativeSingular.init(config);
// Set up deep link handler using event emitter
const emitter = new NativeEventEmitter(NativeSingular);
const linkListener = emitter.addListener('SingularLinkHandler', (params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Clean up event listener on unmount
return () => {
linkListener.remove();
};
}, []);
return (
// Your app components
null
);
}
import React, { useEffect } from 'react';
import { Singular, SingularConfig } from 'singular-react-native';
function App() {
useEffect(() => {
const config = new SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
)
.withSingularLink((params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Initialize SDK
Singular.init(config);
}, []);
return (
// Your app components
);
}
참고: withSingularLink 콜백은 앱이 Singular 링크를 통해 열릴 때만 트리거됩니다. 자세한 내용은 Singular 링크 FAQ를 참조하세요.
전체 메서드 문서에 대한 자세한 내용은 withSingular링크 참조를 참조하세요.
엑스포 구현
구성 파일과 사용자 지정 플러그인을 사용하여 엑스포 프로젝트에 대한 딥링킹을 구성하여 네이티브 코드 수정을 자동으로 처리합니다.
앱 구성
딥링킹을 위한 App.json 업데이트
엑스포는 네이티브 코드 관리에서 추상화되었으므로 app.json 파일에 플랫폼별 딥링킹 구성을 추가하세요.
iOS 구성
- Singular 추적 링크 도메인에 associatedDomains 기능을 추가합니다.
- 사용자 정의 URL 체계 지원을 위한 체계 추가(폴백)
안드로이드 구성
-
Singular 링크 도메인과 일치하도록 호스트 값을 업데이트합니다(예:
example.sng.link). - 기존 스키마 링크 지원을 위해 마지막 의도 필터에서 스키마를 구성합니다.
{
"expo": {
"name": "MySimpleApp",
"slug": "mysimpleapp",
"scheme": "ios-app-scheme",
"ios": {
"associatedDomains": [
"applinks:example.sng.link"
]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/F"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/F"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
},
{
"action": "VIEW",
"data": [
{
"scheme": "android-app-scheme"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
}
]
}
}
}
사용자 정의 엑스포 플러그인
플랫폼별 플러그인 생성
이러한 플러그인 파일은 엑스포의 관리형 워크플로우에서 Singular SDK 연동에 필요한 네이티브 코드 수정을 자동화하여 각 expo prebuild 이후 수동으로 편집할 필요가 없습니다.
- iOS 플러그인: 스위프트 또는 오브젝티브-C 앱디렉티브를 감지하고 어트리뷰션 데이터를 Singular 리액트 네이티브 브리지로 전달하는 임포트 문, 기기 식별자 로깅, 유니버설 링크 처리 코드를 삽입합니다.
-
안드로이드 플러그인: 자바 또는 코틀린 메인액티비티 파일을 감지하고 임포트, 인텐트 로깅, 딥링크를 캡처하여 Singular의 브리지 모듈로 전달하는
onNewIntent메서드를 추가합니다.
두 플러그인 모두 사용자가 딥링크 또는 유니버설 링크를 탭하면 네이티브 코드가 이 데이터를 적절히 캡처하여 어트리뷰션 추적을 위해 React Native 훅으로 전달합니다.
설정 단계
- 프로젝트의 루트 디렉토리로 이동합니다.
- 플러그인 폴더를 생성합니다.
- 다음 JavaScript 플러그인 파일을 추가합니다.
iOS 플러그인 구현
plugins/singular-ios-deeplink-plugin.js 을 생성하여 iOS 딥링킹을 자동으로 구성합니다.
// plugins/singular-ios-deeplink-plugin.js
const { withAppDelegate } = require('@expo/config-plugins');
const withUniversalSingular = (config) => {
return withAppDelegate(config, (config) => {
const { modResults } = config;
// Detect file type
const isSwift = modResults.contents.includes('import Expo') ||
modResults.contents.includes('class AppDelegate');
const isObjectiveC = modResults.contents.includes('#import') ||
modResults.contents.includes('@implementation');
console.log(`Detected AppDelegate type: ${isSwift ? 'Swift' : isObjectiveC ? 'Objective-C' : 'Unknown'}`);
if (isSwift) {
modResults.contents = applySwiftModifications(modResults.contents);
} else if (isObjectiveC) {
modResults.contents = applyObjectiveCModifications(modResults.contents);
} else {
console.warn('Unable to detect AppDelegate type - skipping Singular modifications');
}
return config;
});
};
function applySwiftModifications(contents) {
// Add Singular import
if (!contents.includes('import Singular')) {
contents = contents.replace(
/import ReactAppDependencyProvider/,
`import ReactAppDependencyProvider
import Singular`
);
}
// Add IDFV logging
const swiftLaunchPattern = /didFinishLaunchingWithOptions.*?\) -> Bool \{/s;
if (!contents.includes('UIDevice.current.identifierForVendor')) {
contents = contents.replace(
swiftLaunchPattern,
`didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
print("IDFV", UIDevice.current.identifierForVendor!.uuidString)`
);
}
// Add universal link handling
if (!contents.includes('Universal link received for Singular')) {
contents = contents.replace(
/continue userActivity: NSUserActivity,\s*restorationHandler: @escaping \(\[UIUserActivityRestoring\]\?\) -> Void\s*\) -> Bool \{/,
`continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if let url = userActivity.webpageURL {
print("Universal link received for Singular:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}`
);
}
return contents;
}
function applyObjectiveCModifications(contents) {
// Add Singular import
if (!contents.includes('#import <Singular-React-Native/SingularBridge.h>')) {
contents = contents.replace(
/#import "AppDelegate.h"/,
`#import "AppDelegate.h"
#import <Singular-React-Native/SingularBridge.h>`
);
}
// Add IDFV logging to didFinishLaunchingWithOptions
const objcLaunchPattern = /- \(BOOL\)application:\(UIApplication \*\)application didFinishLaunchingWithOptions:\(NSDictionary \*\)launchOptions\s*{/;
if (!contents.includes('NSLog(@"IDFV %@"')) {
contents = contents.replace(
objcLaunchPattern,
`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"IDFV %@", [UIDevice currentDevice].identifierForVendor.UUIDString);
[SingularBridge startSessionWithLaunchOptions:launchOptions];`
);
}
// Add continueUserActivity method if not present
if (!contents.includes('continueUserActivity')) {
const methodToAdd = `
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
NSLog(@"Universal link received for Singular: %@", userActivity.webpageURL.absoluteString);
[SingularBridge startSessionWithUserActivity:userActivity];
return YES;
}`;
contents = contents.replace(
/@end$/,
`${methodToAdd}
@end`
);
}
return contents;
}
module.exports = withUniversalSingular;
안드로이드 플러그인 구현
plugins/singular-android-deeplink-plugin.js 을 생성하여 안드로이드 딥링킹을 자동으로 구성합니다.
// plugins/singular-android-deeplink-plugin.js
const { withMainActivity } = require('@expo/config-plugins');
const withSingularAndroid = (config) => {
// Add MainActivity modifications
config = withMainActivity(config, (config) => {
const { modResults } = config;
// Detect Java vs Kotlin
const isKotlin = modResults.contents.includes('class MainActivity') &&
modResults.contents.includes('override fun');
const isJava = modResults.contents.includes('public class MainActivity') &&
modResults.contents.includes('@Override');
if (isKotlin) {
modResults.contents = applyKotlinModifications(modResults.contents);
} else if (isJava) {
modResults.contents = applyJavaModifications(modResults.contents);
}
return config;
});
return config;
};
function applyKotlinModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?\n/,
`$&
import android.content.Intent
import net.singular.react_native.SingularBridgeModule`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
function applyJavaModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?;/,
`$&
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
module.exports = withSingularAndroid;
플러그인 등록
app.json 파일을 업데이트하여 Singular 리액트 네이티브 플러그인과 함께 새로운 커스텀 플러그인을 포함하세요.
{
"expo": {
"plugins": [
"singular-react-native",
"./plugins/singular-ios-deeplink-plugin.js",
"./plugins/singular-android-deeplink-plugin.js"
]
}
}
엑스포용 SDK 구성
베어 리액트 네이티브와 동일한 접근 방식을 사용하여 엑스포 앱에서 withSingularLink 콜백을 구성합니다. 전체 구현 세부 사항은 위의 SDK 구성 섹션을 참조하세요.
핸들러 동작
링크 확인 이해
withSingularLink 핸들러는 앱이 새로 설치되었는지 또는 이미 설치되었는지에 따라 다르게 동작합니다.
새로 설치(디퍼드 딥링크)
새로 설치한 경우 앱이 실행될 때 열기 URL이 존재하지 않습니다. 추적 링크에 딥링크 또는 디퍼드 딥링크 값이 포함되어 있는지 확인하기 위해 Singular 어트리뷰션이 완료됩니다.
디퍼드 딥링크 흐름:
- 사용자가 딥링크 값으로 구성된 Singular 트래킹 링크를 클릭합니다.
- 사용자가 앱을 설치하고 처음 실행합니다.
- Singular SDK가 첫 번째 세션을 Singular 서버로 전송합니다.
- 어트리뷰션이 추적 링크에서 딥링크를 완성하고 식별합니다.
-
딥링크 값이
deeplink파라미터의withSingularLink핸들러에isDeferred = true로 반환됩니다.
디퍼드 딥링크 테스트:
- 테스트 기기에서 앱을 제거합니다(현재 앱이 설치되어 있는 경우).
- iOS: IDFA를 재설정합니다. Android:구글 광고 ID(GAID)를 재설정합니다.
- 기기에서 Singular 추적 링크를 클릭합니다(딥링크 값으로 구성되어 있는지 확인).
- 앱을 설치하고 엽니다.
어트리뷰션이 성공적으로 완료되고 디퍼드 딥링크 값이 withSingularLink 핸들러로 전달됩니다.
프로 팁: 다른 패키지 이름(예: com.example.prod 대신 com.example.dev)을 사용하는 개발 빌드로 딥링크를 테스트하는 경우, 개발 앱의 패키지 이름에 맞게 추적 링크를 구성하세요. 테스트 링크를 클릭한 후 앱 스토어에서 프로덕션 앱을 다운로드하지 않고 Android Studio 또는 Xcode를 통해 기기에 직접 개발 빌드를 설치합니다.
이미 설치됨(즉시 딥 링크)
앱이 이미 설치되어 있는 경우 Singular 링크를 클릭하면 유니버설 링크(iOS) 또는 Android 앱 링크 기술을 사용하여 앱이 즉시 열립니다.
즉시 딥링크 흐름:
- 사용자가 Singular 추적 링크를 클릭합니다.
- 운영 체제는 전체 Singular 추적 링크가 포함된 오픈 URL을 제공합니다.
- SDK 초기화 중에 Singular가 URL을 구문 분석합니다.
-
Singular가
deeplink및passthrough값을 추출합니다. -
값은
isDeferred = false을 사용하여withSingularLink핸들러를 통해 반환됩니다.
고급 기능
패스스루 매개변수
패스스루 매개변수를 사용하여 추적 링크 클릭에서 추가 데이터를 캡처합니다.
추적 링크에 passthrough (_p) 파라미터가 포함된 경우 withSingularLink 핸들러의 passthrough파라미터에 해당 데이터가 포함됩니다. 이를 사용하여 캠페인 메타데이터, 사용자 세분화 데이터 또는 앱에서 필요한 사용자 지정 정보를 캡처할 수 있습니다.
패스스루 값 예시:
{
"utm_source": "Facebook",
"utm_campaign": "Web-to-App US RON",
"utm_creative": "Blue CTA for Promo",
"promo_code": "45k435hg"
}
(_p) 파라미터에 URL로 인코딩된 패스스루 값이 포함된 추적 링크 예시입니다:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_p=%7B%22utm_source%22%3A%20%22Facebook%22%2C%22utm_campaign%22%3A%20%22Web-to-App%20US%20RON%22%2C%22utm_creative%22%3A%20%22Blue%20CTA%20for%20Promo%22%2C%22promo_code%22%3A%20%2245k435hg%22%7D
withSingularLink 핸들러가 수신합니다:
urlParameters = {"utm_source": "Facebook","utm_campaign": "Web-to-App US RON","utm_creative": "Blue CTA for Promo","promo_code": "45k435hg"}
모든 쿼리 매개변수 전달
추적 링크에 _forward_params=2 파라미터를 추가하여 추적 링크 URL에서 모든 쿼리 파라미터를 캡처합니다.
_forward_params=2 을 추적 링크에 추가하면 모든 쿼리 파라미터가 withSingularLink 처리기의 deeplink 파라미터에 포함되므로 모든 파라미터가 포함된 전체 URL에 액세스할 수 있습니다.
추적 링크 예시:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_forward_params=2&utm_source=facebook&promo=SALE2024
withSingularLink 핸들러가 수신합니다:
deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"