Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Please use Github issues, Pull Requests, or feel free to reach out to our [suppo

Interested in integrating your service with us? Check out our [Partners page](https://segment.com/partners/) for more details.

### For guidelines on API integrations, please refer to [this](docs/API_GUIDELINES.md) page

## License
```
MIT License
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
classpath("org.jetbrains.kotlin:kotlin-serialization:1.8.0")
classpath("com.android.tools.build:gradle:7.0.4")
classpath("com.android.tools.build:gradle:8.5.1")
}
}

Expand Down
122 changes: 122 additions & 0 deletions docs/API_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
## <a id="dma_support"> Send consent for DMA compliance
For a general introduction to DMA consent data, see [here](https://dev.appsflyer.com/hc/docs/send-consent-for-dma-compliance).<be>
The AppsFlyer SDK offers two alternative methods for gathering consent data:<br>
- **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>
OR<br>
- **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.
### Use CMP to collect consent data
A CMP compatible with TCF v2.2 collects DMA consent data and stores it in <code>SharedPreferences</code>. To enable the SDK to access this data and include it with every event, follow these steps:<br>
<ol>
<li> Create AppsFlyer plugin object <code>val appsFlyerDestination = AppsFlyerDestination(this, true) in the Activity class</code>
<li> Call <code>appsFlyerDestination.enableTCFDataCollection = true</code> to instruct the AppsFlyer SDK to collect the TCF data from the device.
<li> Call <code>appsFlyerDestination.startAppsFlyerManually = true</code>. <br> This will allow us to delay the Conversion call in order to provide the SDK with the user consent.
<li> In the <code>Activity</code> class, use the CMP to decide if you need the consent dialog in the current session.
<li> If needed, show the consent dialog, using the CMP, to capture the user consent decision. Otherwise, go to step 7.
<li> Get confirmation from the CMP that the user has made their consent decision, and the data is available in <code>SharedPreferences</code>.
<li> Call <code>AppsFlyerLib.getInstance().start(this)</code>
</ol>

#### Activity class
```kotlin
class MainActivity: Activity() {

private val consentRequired = true

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

val analytics = Analytics("dev_key", applicationContext) {
this.flushAt = 3
this.trackApplicationLifecycleEvents = true
}

val appsFlyerDestination = AppsFlyerDestination(this, true)
if (consentRequired) {
appsFlyerDestination.enableTCFDataCollection = true
appsFlyerDestination.startAppsFlyerManually = true
initConsentCollection()
}
analytics.add(plugin = appsFlyerDestination)
}

private fun initConsentCollection() {
// Implement here the you CMP flow
// When the flow is completed and consent was collected
// call onConsentCollectionFinished()
}

private fun onConsentCollectionFinished() {
AppsFlyerLib.getInstance().start(this)
}
}
```

### Manually collect consent data
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.
<ol>
<li> Create AppsFlyer plugin object <code>val appsFlyerDestination = AppsFlyerDestination(this, true) in the Activity class</code>
<li> In the <code>Activity</code> class, determine whether the GDPR applies or not to the user.<br>
- If GDPR applies to the user, perform the following:
<ol>
<li> Call <code>appsFlyerDestination.startAppsFlyerManually = true</code>. <br> This will allow us to delay the Conversion call in order to provide the SDK with the user consent.
<li> Given that GDPR applies to the user, determine whether the consent data is already stored for this session.
<ol>
<li> If there is no consent data stored, show the consent dialog to capture the user consent decision.
<li> If there is consent data stored, continue to the next step.
</ol>
<li> To transfer the consent data to the SDK, create an object called AppsFlyerConsent with the following optional parameters:<br>
- <code>isUserSubjectToGDPR</code> - Indicates whether GDPR applies to the user.<br>
- <code>hasConsentForDataUsage</code> - Indicates whether the user has consented to use their data for advertising purposes.<br>
- <code>hasConsentForAdsPersonalization</code> - Indicates whether the user has consented to use their data for personalized advertising purposes.<br>
- <code>hasConsentForAdStorage</code> - Indicates whether the user has consented to store or access information on a device.<br>
<li> Call <code>AppsFlyerLib.getInstance().setConsentData()</code> with the <code>AppsFlyerConsent</code> object.
<li> Call <code>AppsFlyerLib.getInstance().start(this)</code>.
</ol><br>
- If GDPR does not apply to the user, set <code>isUserSubjectToGDPR</code> to false and the rest of the parameters must be null. See example below:
<ol>
<li> Create an <code>AppsFlyerConsent</code> object:<br> <code>val nonGdprUser = AppsFlyerConsent(false, null, null, null)</code>
<li> Call <br><code>AppsFlyerLib.getInstance().setConsentData(nonGdprUser)</code>
</ol>
<li> Call <code>analytics.add(plugin = appsFlyerDestination)</code> <br>


#### Activity class
```kotlin
class MainActivity: Activity() {

private val consentRequired = true

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val analytics = Analytics("dev_key",applicationContext) {
this.flushAt = 3
this.trackApplicationLifecycleEvents = true
}

val appsFlyerDestination = AppsFlyerDestination(this, true)
if (consentRequired) {
appsFlyerDestination.startAppsFlyerManually = true
presentConsentCollectionDialog()
} else {
val nonGdprUser = AppsFlyerConsent(false, null, null, null)
AppsFlyerLib.getInstance().setConsentData(nonGdprUser)
}
analytics.add(plugin = appsFlyerDestination)
}

private fun presentConsentCollectionDialog() {
// When the flow is completed and consent data was collected
// call onConsentCollectionFinished(consent)
}

private fun onConsentCollectionFinished(consent: AppsFlyerConsent) {
AppsFlyerLib.getInstance().setConsentData(consent)
AppsFlyerLib.getInstance().start(this)
}
}
```
</ol>

3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Tue Sep 09 15:01:29 EEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
16 changes: 11 additions & 5 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ val VERSION_NAME: String by project

android {
compileSdk = 33
namespace = "com.segment.analytics.kotlin.destinations.appsflyer"

defaultConfig {
multiDexEnabled = true
minSdk = 16
minSdk = 19
targetSdk = 33

testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
Expand All @@ -30,15 +31,20 @@ android {
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}

buildFeatures {
buildConfig = true
}
}

dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("com.segment.analytics.kotlin:android:1.14.2")
implementation("androidx.multidex:multidex:2.0.1")

Expand All @@ -51,7 +57,7 @@ dependencies {

// Partner Dependencies
dependencies {
implementation("com.appsflyer:af-android-sdk:6.13.0")
implementation ("com.appsflyer:af-android-sdk:6.17.3")
implementation ("com.android.installreferrer:installreferrer:2.2")
}

Expand Down
3 changes: 1 addition & 2 deletions lib/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.segment.analytics.kotlin.destinations.appsflyer">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ data class AppsFlyerSettings(
)

class AppsFlyerDestination(
private val applicationContext: Context,
private val activityContext: Context,
private var isDebug: Boolean = false
) : DestinationPlugin(), AndroidLifecycle, VersionedPlugin {

Expand All @@ -72,6 +72,9 @@ class AppsFlyerDestination(
var conversionListener: ExternalAppsFlyerConversionListener? = null
var deepLinkListener: ExternalDeepLinkListener? = null

var startAppsFlyerManually: Boolean = false
var enableTCFDataCollection: Boolean? = null

override val key: String = "AppsFlyer"

override fun setup(analytics: Analytics) {
Expand All @@ -91,8 +94,17 @@ class AppsFlyerDestination(
if (it.trackAttributionData) {
listener = ConversionListener()
}
appsflyer?.setDebugLog(isDebug)
appsflyer?.init(it.appsFlyerDevKey, listener, applicationContext)
appsflyer?.apply {
setDebugLog(isDebug)
init(it.appsFlyerDevKey, listener, activityContext)
enableTCFDataCollection?.let { enabled ->
enableTCFDataCollection(enabled)
}
if (!startAppsFlyerManually) {
start(activityContext)
analytics.log("AppsFlyerLib.getInstance().start($activityContext)")
}
}
}
}
deepLinkListener?.let {
Expand All @@ -119,17 +131,13 @@ class AppsFlyerDestination(

val afProperties = properties.mapTransform(propertiesMapper).mapValues { (_, v) -> v.toContent() }

appsflyer?.logEvent(applicationContext, event, afProperties)
appsflyer?.logEvent(activityContext, event, afProperties)
analytics.log("appsflyer.logEvent(context, $event, $properties)")
return payload
}

override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
super.onActivityCreated(activity, savedInstanceState)
if (activity != null) {
AppsFlyerLib.getInstance().start(activity)
analytics.log("AppsFlyerLib.getInstance().start($activity)")
}
updateEndUserAttributes()
}

Expand Down Expand Up @@ -227,13 +235,13 @@ class AppsFlyerDestination(

private fun getFlag(key: String): Boolean {
val sharedPreferences: SharedPreferences =
applicationContext.getSharedPreferences(AF_SEGMENT_SHARED_PREF, 0)
activityContext.getSharedPreferences(AF_SEGMENT_SHARED_PREF, 0)
return sharedPreferences.getBoolean(key, false)
}

private fun setFlag(key: String, value: Boolean) {
val sharedPreferences: SharedPreferences =
applicationContext.getSharedPreferences(AF_SEGMENT_SHARED_PREF, 0)
activityContext.getSharedPreferences(AF_SEGMENT_SHARED_PREF, 0)
val editor = sharedPreferences.edit()
editor.putBoolean(key, value).apply()
}
Expand Down