Skip to content

Commit 61e93fd

Browse files
Update Analytics-Appsflyer Plugin. (#23)
* Update README.md * Update AppsFlyerDestination.swift * Update BasicExampleApp.swift * Update Package.swift * Update BasicExampleApp.swift * Update Package.swift * Update Package.swift * Update Package.swift * Update Package.swift * Update README.md
1 parent d466378 commit 61e93fd

File tree

4 files changed

+283
-17
lines changed

4 files changed

+283
-17
lines changed

Example/BasicExample/BasicExample/BasicExampleApp.swift

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,69 @@ import SwiftUI
99
import Segment
1010
import SegmentAppsFlyer
1111
import AppsFlyerLib
12+
import AppTrackingTransparency
1213

1314
@main
1415
struct BasicExampleApp: App {
16+
static var analytics: Analytics? = nil
17+
static var appsflyerDest: AppsFlyerDestination!
18+
static var afDelegate: AFDelgate!
19+
init() {
20+
// Initialize delegate first and store it
21+
BasicExampleApp.afDelegate = AFDelgate()
22+
BasicExampleApp.analytics = Analytics(configuration: Configuration(writeKey: "<WRITE_KEY>")
23+
.flushAt(3)
24+
.trackApplicationLifecycleEvents(true)
25+
)
26+
27+
AppsFlyerLib.shared().isDebug = true
28+
//If waiting to ATT status please wait for it using the below line.
29+
//AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
30+
31+
// Use the stored delegate
32+
BasicExampleApp.appsflyerDest = AppsFlyerDestination(
33+
segDelegate: BasicExampleApp.afDelegate,
34+
segDLDelegate: BasicExampleApp.afDelegate,
35+
)
36+
BasicExampleApp.analytics?.add(plugin: NewAnalyticsAppsflyerIntegrationApp.appsflyerDest)
37+
}
38+
39+
func requestTrackingAuthorization() {
40+
ATTrackingManager.requestTrackingAuthorization { status in
41+
switch status {
42+
case .authorized:
43+
// Tracking authorization granted
44+
break
45+
case .denied, .notDetermined, .restricted:
46+
// Handle the case when permission is denied or not yet determined
47+
break
48+
@unknown default:
49+
break
50+
}
51+
}
52+
}
53+
1554
var body: some Scene {
1655
WindowGroup {
1756
ContentView()
57+
.onOpenURL { url in
58+
// Handle deep links in SwiftUI
59+
print("Deep link received: \(url)")
60+
AppsFlyerLib.shared().handleOpen(url, options: nil)
61+
}
1862
}
1963
}
2064
}
2165

22-
class DeepLinkManager: NSObject, DeepLinkDelegate {
66+
class AFDelgate: NSObject, AppsFlyerLibDelegate, DeepLinkDelegate{
67+
func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) {
68+
print("moris testing onConversionDataSuccess")
69+
}
70+
71+
func onConversionDataFail(_ error: any Error) {
72+
print("moris testing onConversionDataFail")
73+
}
2374
func didResolveDeepLink(_ result: DeepLinkResult) {
2475
print("Deep Link: \(result)")
2576
}
2677
}
27-
28-
extension Analytics {
29-
static var main: Analytics {
30-
let analytics = Analytics(configuration: Configuration(writeKey: "<YOUR WRITE KEY>")
31-
.flushAt(3)
32-
.trackApplicationLifecycleEvents(true))
33-
34-
let deepLinkHandler = DeepLinkManager()
35-
let appsFlyer = AppsFlyerDestination(segDLDelegate: deepLinkHandler)
36-
analytics.add(plugin: appsFlyer)
37-
return analytics
38-
}
39-
}

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ let package = Package(
2727
.package(
2828
name: "AppsFlyerLib-Dynamic",
2929
url: "https://github.com/AppsFlyerSDK/AppsFlyerFramework-Dynamic",
30-
from: "6.14.0"
30+
.exact("6.17.0")
3131
)
3232
],
3333
targets: [

README.md

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Add AppsFlyer device mode support to your applications via this plugin for [Anal
44

55
⚠️ **Github Issues disabled in this repository** ⚠️
66

7-
Please direct all issues, bug reports, and feature enhancements to `friends@segment.com` so they can be resolved as efficiently as possible.
7+
Please direct Segment issues, bug reports, and feature enhancements to `friends@segment.com` so they can be resolved as efficiently as possible.
8+
Please direct Appsflyer issues, bug reports, and feature enhancements to `support@appsflyer.com` so they can be resolved as efficiently as possible.
89

910
## Adding the dependency
1011

@@ -53,6 +54,230 @@ analytics.add(plugin: AppsFlyerDestination())
5354

5455
Your events will now begin to flow to AppsFlyer in device mode.
5556

57+
## Appsflyer SDK documentation
58+
Please go here to see the Appsflyer Native iOS documentation [here](https://dev.appsflyer.com/hc/docs/ios-sdk)
59+
60+
## <a id="manual"> Manual mode
61+
We support a manual mode to seperate the initialization of the AppsFlyer SDK and the start of the SDK. In this case, the AppsFlyer SDK won't start automatically, giving the developer more freedom when to start the AppsFlyer SDK. Please note that in manual mode, the developper is required to implement the API ``startAppsflyerSDK()`` in order to start the SDK.
62+
<br>If you are using CMP to collect consent data this feature is needed. See explanation [here](#dma_support).
63+
### Example:
64+
#### swift
65+
```swift
66+
struct NewAnalyticsAppsflyerIntegrationApp: App {
67+
static var analytics: Analytics? = nil
68+
static var appsflyerDest: AppsFlyerDestination!
69+
init() {
70+
self.requestTrackingAuthorization()
71+
NewAnalyticsAppsflyerIntegrationApp.analytics = Analytics(configuration: Configuration(writeKey: "<WRITE_KEY>")
72+
.flushAt(3)
73+
.trackApplicationLifecycleEvents(true)
74+
)
75+
AppsFlyerLib.shared().isDebug = true
76+
// AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
77+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest = AppsFlyerDestination(segDelegate: sfdelegate, segDLDelegate: sfdelegate, manualMode: true)
78+
NewAnalyticsAppsflyerIntegrationApp.analytics?.add(plugin: NewAnalyticsAppsflyerIntegrationApp.appsflyerDest)
79+
}
80+
...
81+
```
82+
83+
To start the AppsFlyer SDK, use the `startAppsflyerSDK()` API, like the following :
84+
#### swift
85+
```swift
86+
// check cmp response or check manually for the User's response.
87+
// if decided to start the Appsflyer SDK manually do it like here:
88+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest.startAppsflyerSDK()
89+
```
90+
91+
## <a id="getconversiondata"> Get Conversion Data
92+
93+
In order for Conversion Data to be sent to Segment, make sure you have enabled "Track Attribution Data" and specified App ID in AppsFlyer destination settings:
94+
95+
![image](https://user-images.githubusercontent.com/50541317/69795158-51b86780-11d4-11ea-9ab3-be3e669e4e3b.png)
96+
97+
### <a id="gcd-swift"> Swift
98+
99+
In order to get Conversion Data you need to:
100+
101+
1. Create a class applies the AppsFlyerLibDelegate delgeate
102+
2. Pass the initialized class to the AppsflyerDestination
103+
3. Implement methods of the protocol in the class, passed as a delegate. See sample code below where AppDelegate is used for that:
104+
105+
```swift
106+
struct NewAnalyticsAppsflyerIntegrationApp: App {
107+
static var afDelegate: AFDelgate! // Add strong reference to delegate
108+
109+
init() {
110+
...
111+
NewAnalyticsAppsflyerIntegrationApp.afDelegate = AFDelgate()
112+
113+
NewAnalyticsAppsflyerIntegrationApp.analytics = Analytics(configuration: Configuration(writeKey: "<WRITE_KEY>")
114+
.flushAt(3)
115+
.trackApplicationLifecycleEvents(true)
116+
)
117+
118+
AppsFlyerLib.shared().isDebug = true
119+
120+
// Use the stored delegate
121+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest = AppsFlyerDestination(
122+
segDelegate: NewAnalyticsAppsflyerIntegrationApp.afDelegate,
123+
segDLDelegate: nil
124+
)
125+
NewAnalyticsAppsflyerIntegrationApp.analytics?.add(plugin: NewAnalyticsAppsflyerIntegrationApp.appsflyerDest)
126+
}
127+
...
128+
...
129+
class AFDelgate: NSObject, AppsFlyerLibDelegate{
130+
func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) {
131+
print("moris testing onConversionDataSuccess")
132+
}
133+
134+
func onConversionDataFail(_ error: any Error) {
135+
print("moris testing onConversionDataFail")
136+
}
137+
}
138+
```
139+
140+
## <a id="DDL"> Unified Deep linking
141+
### <a id="ddl-swift"> Swift
142+
In order to use Unified Deep linking you need to:
143+
144+
1. Create a class applies the DeepLinkDelegate delgeate
145+
2. Pass the initialized class to the AppsflyerDestination
146+
```swift
147+
let factoryWithDelegate: SEGAppsFlyerIntegrationFactory = SEGAppsFlyerIntegrationFactory.create(withLaunch: self, andDeepLinkDelegate: self)
148+
```
149+
150+
3. Implement methods of the protocol in the class, passed as a delegate. See sample code below where AppDelegate is used for that:
151+
152+
```swift
153+
struct NewAnalyticsAppsflyerIntegrationApp: App {
154+
static var afDelegate: AFDelgate! // Add strong reference to delegate
155+
156+
init() {
157+
...
158+
NewAnalyticsAppsflyerIntegrationApp.afDelegate = AFDelgate()
159+
160+
NewAnalyticsAppsflyerIntegrationApp.analytics = Analytics(configuration: Configuration(writeKey: "<WRITE_KEY>")
161+
.flushAt(3)
162+
.trackApplicationLifecycleEvents(true)
163+
)
164+
165+
AppsFlyerLib.shared().isDebug = true
166+
167+
// Use the stored delegate
168+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest = AppsFlyerDestination(
169+
segDelegate: nil,
170+
segDLDelegate: NewAnalyticsAppsflyerIntegrationApp.afDelegate
171+
)
172+
NewAnalyticsAppsflyerIntegrationApp.analytics?.add(plugin: NewAnalyticsAppsflyerIntegrationApp.appsflyerDest)
173+
}
174+
...
175+
...
176+
class AFDelgate: NSObject, DeepLinkDelegate{
177+
func didResolveDeepLink(_ result: DeepLinkResult) {
178+
print("Deep Link: \(result)")
179+
}
180+
}
181+
```
182+
183+
## <a id="dma_support"> Send consent for DMA compliance
184+
**important:** As of Appsflyer SDK 6.17.0 there are additions in the Appsflyer SDK API on how to use DMA, [see here](https://dev.appsflyer.com/hc/docs/ios-send-consent-for-dma-compliance).<br>
185+
The SDK offers two alternative methods for gathering consent data:<br>
186+
- **Through a Consent Management Platform (CMP)**: If the app uses a CMP that complies with the [Transparency and Consent Framework (TCF) v2.2 protocol](https://iabeurope.eu/tcf-supporting-resources/), the SDK can automatically retrieve the consent details.<br>
187+
<br>OR<br><br>
188+
- **Through a dedicated SDK API**: Developers can pass Google's required consent data directly to the SDK using a specific API designed for this purpose.
189+
### Use CMP to collect consent data
190+
A CMP compatible with TCF v2.2 collects DMA consent data and stores it in <code>NSUserDefaults</code>. To enable the SDK to access this data and include it with every event, follow these steps:<br>
191+
<ol>
192+
<li> Call <code>AppsFlyerLib.shared().enableTCFDataCollection(true)</code> to instruct the SDK to collect the TCF data from the device.
193+
<li> Initialize <code>AppsFlyerDestination</code> using manualMode = true. This will allow us to delay the Conversion call in order to provide the SDK with the user consent.
194+
<li> In the <code>applicationDidBecomeActive</code> lifecycle method, use the CMP to decide if you need the consent dialog in the current session to acquire the consent data. If you need the consent dialog move to step 4; otherwise move to step 5.
195+
<li> Get confirmation from the CMP that the user has made their consent decision and the data is available in <code>NSUserDefaults</code>.
196+
<li> Call <code>startAppsflyerSDK()</code>.
197+
</ol>
198+
199+
200+
```swift
201+
202+
static var analytics: Analytics? = nil
203+
static var appsflyerDest: AppsFlyerDestination!
204+
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
205+
206+
// For AppsFLyer debug logs uncomment the line below
207+
AppsFlyerLib.shared().isDebug = true
208+
AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
209+
AppsFlyerLib.shared().enableTCFDataCollection(true)
210+
NewAnalyticsAppsflyerIntegrationApp.analytics = Analytics(configuration: Configuration(writeKey: "<WRITE_KEY>")
211+
.flushAt(3)
212+
.trackApplicationLifecycleEvents(true)
213+
)
214+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest = AppsFlyerDestination(segDelegate: sfdelegate, segDLDelegate: sfdelegate, manualMode: true)
215+
NewAnalyticsAppsflyerIntegrationApp.analytics?.add(plugin: NewAnalyticsAppsflyerIntegrationApp.appsflyerDest)
216+
return true
217+
}
218+
219+
func applicationDidBecomeActive(_ application: UIApplication) {
220+
if(cmpManager!.hasConsent()){
221+
//CMP manager already has consent ready - you can start
222+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest.startAppsflyerSDK()
223+
}else{
224+
//CMP doesn't have consent data ready yet
225+
//Waiting for CMP completion and data ready and then start
226+
cmpManager?.withOnCmpButtonClickedCallback({ CmpButtonEvent in
227+
NewAnalyticsAppsflyerIntegrationApp.appsflyerDest.startAppsflyerSDK()
228+
})
229+
}
230+
231+
if #available(iOS 14, *) {
232+
ATTrackingManager.requestTrackingAuthorization { (status) in
233+
switch status {
234+
case .denied:
235+
print("AuthorizationSatus is denied")
236+
case .notDetermined:
237+
print("AuthorizationSatus is notDetermined")
238+
case .restricted:
239+
print("AuthorizationSatus is restricted")
240+
case .authorized:
241+
print("AuthorizationSatus is authorized")
242+
@unknown default:
243+
fatalError("Invalid authorization status")
244+
}
245+
}
246+
}
247+
}
248+
```
249+
250+
### Manually collect consent data
251+
If your app does not use a CMP compatible with TCF v2.2, use the SDK API detailed below to provide the consent data directly to the SDK.
252+
<ol>
253+
<li> Initialize <code>AppsFlyerDestination</code> using manual mode. This will allow us to delay the Conversion call in order to provide the SDK with the user consent.
254+
<li> In the <code>applicationDidBecomeActive</code> lifecycle method determine whether the GDPR applies or not to the user.<br>
255+
- If GDPR applies to the user, perform the following:
256+
<ol>
257+
<li> Given that GDPR is applicable to the user, determine whether the consent data is already stored for this session.
258+
<ol>
259+
<li> If there is no consent data stored, show the consent dialog to capture the user consent decision.
260+
<li> If there is consent data stored continue to the next step.
261+
</ol>
262+
<li> To transfer the consent data to the SDK create an AppsFlyerConsent object with the following parameters:<br>
263+
- <code>forGDPRUserWithHasConsentForDataUsage</code>- Indicates whether the user has consented to use their data for advertising purposes.
264+
- <code>hasConsentForAdsPersonalization</code>- Indicates whether the user has consented to use their data for personalized advertising.
265+
<li> Call <code>AppsFlyerLib.shared().setConsentData(AppsFlyerConsent(forGDPRUserWithHasConsentForDataUsage: Bool, hasConsentForAdsPersonalization: Bool))</code>.
266+
<li> Call <code>NewAnalyticsAppsflyerIntegrationApp.appsflyerDest.startAppsflyerSDK()</code>.
267+
</ol><br>
268+
- If GDPR doesn’t apply to the user perform the following:
269+
<ol>
270+
<li> Call <code>AppsFlyerLib.shared().setConsentData(AppsFlyerConsent(nonGDPRUser: ()))</code>.
271+
<li> It is optional to initialize <code>AppsFlyerDestination</code> using manual mode not mandatory as before.
272+
</ol>
273+
</ol>
274+
275+
276+
## <a id="usage"> Usage
277+
278+
First of all, you must provide values for AppsFlyer Dev Key, Apple App ID (iTunes) and client secret in Segment's **dashboard** for AppsFlyer integration
279+
280+
56281

57282
## Support
58283

Sources/SegmentAppsFlyer/AppsFlyerDestination.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ public class AppsFlyerDestination: UIResponder, DestinationPlugin {
162162
event.event == "Organic Install" ||
163163
event.event == "Deep Link Opened" ||
164164
event.event == "Direct Deep Link" ||
165-
event.event == "Deferred Deep Link"){
165+
event.event == "Deferred Deep Link" ||
166+
event.event == "Application Backgrounded" ||
167+
event.event == "Application Opened" ||
168+
event.event == "Application Foregrounded" ){
166169
return nil
167170
}
168171
var properties = event.properties?.dictionaryValue

0 commit comments

Comments
 (0)