
Kotlin Multiplatform wrapper for the AppsFlyer SDK (Android + iOS)
Experimental & Unofficial
This is a personal, unofficial project and is not affiliated with or endorsed by AppsFlyer. It is experimental and may contain bugs. Use in production at your own risk.
AppsFlyer and the AppsFlyer SDK name are trademarks of their respective owners. This project is an independent wrapper and claims no affiliation with or endorsement by AppsFlyer. The underlying AppsFlyer SDKs are governed by their own terms and licenses.
A Kotlin Multiplatform wrapper for the AppsFlyer SDK (Android + iOS). AppsFlyer
ships no official KMP SDK, so this library fills that gap by delegating to the
native SDKs via expect/actual, behind a coroutine-first API.
Asynchronous operations use suspending functions instead of callbacks:
suspend fun getStartResult(): StartResult
suspend fun getConversionData(): CampaignData
suspend fun logEventForResult(name: String, params: Map<String, Any?>): LogEventResult
suspend fun validateAndLogInAppPurchase(...): PurchaseValidationResultDeep link events arrive as a Flow instead of through listeners:
val deepLink: Flow<DeepLinkResult>Null values in logEvent, logAdRevenue, setAdditionalData, setPartnerData,
and validateAndLogInAppPurchase params are silently dropped on both platforms.
dependencies {
implementation("org.retar.appsflyer:appsflyer:0.1.0")
}AppsFlyer.initialize(this, AppsFlyerConfig(
devKey = "YOUR_AF_DEV_KEY",
isDebug = BuildConfig.DEBUG,
))Add an intent filter for deep links in your AndroidManifest.xml using the
standard AppsFlyer OneLink configuration. The SDK handles the rest.
The AppsFlyerLib SPM dependency is declared via spmForKmp in
build.gradle.kts and resolved for you, so there is no manual SPM step.
To expose the AppsFlyer API in Swift, use export in your framework:
kotlin {
iosArm64().binaries.framework {
export("org.retar.appsflyer:appsflyer:0.1.0")
}
}Alternatively, you can add AppsFlyerLib directly via SPM in your Xcode project:
https://github.com/AppsFlyerSDK/AppsFlyerFramework
7.0.0
AppsFlyerLib
Then in Swift, import your framework and use the SDK directly:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
AppsFlyer.shared.initialize(
config: AppsFlyerConfig(
devKey: "YOUR_AF_DEV_KEY",
isDebug: true,
iosAppId: "YOUR_APPLE_APP_ID"
),
launchOptions: launchOptions
)
return true
}
}Forward deep links from your SwiftUI views or AppDelegate:
.onOpenURL { url in AppsFlyer.shared.linkHandler.handleOpenUrl(url: url) }
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
AppsFlyer.shared.linkHandler.handleUserActivity(userActivity: activity)
}// Subscribe to deep links before starting
scope.launch {
AppsFlyer.client.deepLink.collect { result ->
when (result) {
is DeepLinkResult.Found -> navigate(result.deepLinkValue)
is DeepLinkResult.NotFound -> { }
is DeepLinkResult.Error -> log(result.message)
}
}
}
// Start the SDK
AppsFlyer.client.start()
// Observe attribution
scope.launch {
when (val data = AppsFlyer.client.getConversionData()) {
is CampaignData.Success -> log("${data.status} — ${data.mediaSource}")
is CampaignData.Error -> log(data.message)
}
}
// Log events
AppsFlyer.client.logEvent("purchase", mapOf("price" to 9.99))
// Ad revenue
AppsFlyer.client.logAdRevenue(AdRevenueData(
monetizationNetwork = "ironsource",
mediationNetwork = AfMediationNetwork.GOOGLE_ADMOB,
currency = "USD",
revenue = 0.0015,
))
// In-app purchase validation
scope.launch {
val result = AppsFlyer.client.validateAndLogInAppPurchase(
PurchaseDetails(
productId = "com.example.pro",
transactionId = "txn-123",
purchaseType = AfPurchaseType.SUBSCRIPTION,
),
)
}
// GDPR consent
AppsFlyer.initialize(context, AppsFlyerConfig(
devKey = "YOUR_AF_DEV_KEY",
consentData = AppsFlyerConsent.forGDPRUser(
hasConsentForDataUsage = true,
hasConsentForAdsPersonalization = false,
),
))
// Uninstall measurement
AppsFlyer.client.registerUninstall(fcmToken)APIs that exist on only one platform are no-ops on the other (e.g.
setDisableSKAdNetwork is iOS-only, setCollectIMEI is Android-only).
Android-only extensions that require Context/Activity/Intent:
fun AppsFlyerClient.performOnDeepLinking(intent: Intent, context: Context)
fun AppsFlyerClient.sendPushNotificationData(activity: Activity)appsflyer/
src/
commonMain/ # shared API + implementation
androidMain/ # delegates to af-android-sdk
iosMain/ # delegates to AppsFlyerLib via Swift bridge
swift/ # Swift bridge wrapping iOS delegates
samples/
demo-app/ # Compose Multiplatform demo
Credentials are gitignored. Add them to:
local.properties: appsflyer.devKey=... / appsflyer.iosAppId=...
samples/demo-app/iosApp/Secrets.xcconfig: AF_DEV_KEY = ... / AF_IOS_APP_ID = ...
# Android
./gradlew :samples:demo-app:androidApp:assembleDebug
# iOS
cd samples/demo-app/iosApp && xcodegen && open iosApp.xcodeprojorg.retar.appsflyer).AppsFlyerLib SPM dependency
automatically through spmForKmp. Use export in your framework to expose
the API in Swift.Library version is controlled via LIBRARY_VERSION in gradle.properties.
Licensed under the Apache License 2.0.
Experimental & Unofficial
This is a personal, unofficial project and is not affiliated with or endorsed by AppsFlyer. It is experimental and may contain bugs. Use in production at your own risk.
AppsFlyer and the AppsFlyer SDK name are trademarks of their respective owners. This project is an independent wrapper and claims no affiliation with or endorsement by AppsFlyer. The underlying AppsFlyer SDKs are governed by their own terms and licenses.
A Kotlin Multiplatform wrapper for the AppsFlyer SDK (Android + iOS). AppsFlyer
ships no official KMP SDK, so this library fills that gap by delegating to the
native SDKs via expect/actual, behind a coroutine-first API.
Asynchronous operations use suspending functions instead of callbacks:
suspend fun getStartResult(): StartResult
suspend fun getConversionData(): CampaignData
suspend fun logEventForResult(name: String, params: Map<String, Any?>): LogEventResult
suspend fun validateAndLogInAppPurchase(...): PurchaseValidationResultDeep link events arrive as a Flow instead of through listeners:
val deepLink: Flow<DeepLinkResult>Null values in logEvent, logAdRevenue, setAdditionalData, setPartnerData,
and validateAndLogInAppPurchase params are silently dropped on both platforms.
dependencies {
implementation("org.retar.appsflyer:appsflyer:0.1.0")
}AppsFlyer.initialize(this, AppsFlyerConfig(
devKey = "YOUR_AF_DEV_KEY",
isDebug = BuildConfig.DEBUG,
))Add an intent filter for deep links in your AndroidManifest.xml using the
standard AppsFlyer OneLink configuration. The SDK handles the rest.
The AppsFlyerLib SPM dependency is declared via spmForKmp in
build.gradle.kts and resolved for you, so there is no manual SPM step.
To expose the AppsFlyer API in Swift, use export in your framework:
kotlin {
iosArm64().binaries.framework {
export("org.retar.appsflyer:appsflyer:0.1.0")
}
}Alternatively, you can add AppsFlyerLib directly via SPM in your Xcode project:
https://github.com/AppsFlyerSDK/AppsFlyerFramework
7.0.0
AppsFlyerLib
Then in Swift, import your framework and use the SDK directly:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
AppsFlyer.shared.initialize(
config: AppsFlyerConfig(
devKey: "YOUR_AF_DEV_KEY",
isDebug: true,
iosAppId: "YOUR_APPLE_APP_ID"
),
launchOptions: launchOptions
)
return true
}
}Forward deep links from your SwiftUI views or AppDelegate:
.onOpenURL { url in AppsFlyer.shared.linkHandler.handleOpenUrl(url: url) }
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
AppsFlyer.shared.linkHandler.handleUserActivity(userActivity: activity)
}// Subscribe to deep links before starting
scope.launch {
AppsFlyer.client.deepLink.collect { result ->
when (result) {
is DeepLinkResult.Found -> navigate(result.deepLinkValue)
is DeepLinkResult.NotFound -> { }
is DeepLinkResult.Error -> log(result.message)
}
}
}
// Start the SDK
AppsFlyer.client.start()
// Observe attribution
scope.launch {
when (val data = AppsFlyer.client.getConversionData()) {
is CampaignData.Success -> log("${data.status} — ${data.mediaSource}")
is CampaignData.Error -> log(data.message)
}
}
// Log events
AppsFlyer.client.logEvent("purchase", mapOf("price" to 9.99))
// Ad revenue
AppsFlyer.client.logAdRevenue(AdRevenueData(
monetizationNetwork = "ironsource",
mediationNetwork = AfMediationNetwork.GOOGLE_ADMOB,
currency = "USD",
revenue = 0.0015,
))
// In-app purchase validation
scope.launch {
val result = AppsFlyer.client.validateAndLogInAppPurchase(
PurchaseDetails(
productId = "com.example.pro",
transactionId = "txn-123",
purchaseType = AfPurchaseType.SUBSCRIPTION,
),
)
}
// GDPR consent
AppsFlyer.initialize(context, AppsFlyerConfig(
devKey = "YOUR_AF_DEV_KEY",
consentData = AppsFlyerConsent.forGDPRUser(
hasConsentForDataUsage = true,
hasConsentForAdsPersonalization = false,
),
))
// Uninstall measurement
AppsFlyer.client.registerUninstall(fcmToken)APIs that exist on only one platform are no-ops on the other (e.g.
setDisableSKAdNetwork is iOS-only, setCollectIMEI is Android-only).
Android-only extensions that require Context/Activity/Intent:
fun AppsFlyerClient.performOnDeepLinking(intent: Intent, context: Context)
fun AppsFlyerClient.sendPushNotificationData(activity: Activity)appsflyer/
src/
commonMain/ # shared API + implementation
androidMain/ # delegates to af-android-sdk
iosMain/ # delegates to AppsFlyerLib via Swift bridge
swift/ # Swift bridge wrapping iOS delegates
samples/
demo-app/ # Compose Multiplatform demo
Credentials are gitignored. Add them to:
local.properties: appsflyer.devKey=... / appsflyer.iosAppId=...
samples/demo-app/iosApp/Secrets.xcconfig: AF_DEV_KEY = ... / AF_IOS_APP_ID = ...
# Android
./gradlew :samples:demo-app:androidApp:assembleDebug
# iOS
cd samples/demo-app/iosApp && xcodegen && open iosApp.xcodeprojorg.retar.appsflyer).AppsFlyerLib SPM dependency
automatically through spmForKmp. Use export in your framework to expose
the API in Swift.Library version is controlled via LIBRARY_VERSION in gradle.properties.
Licensed under the Apache License 2.0.