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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 4.3.0

- `enableTCFConsentCollection(enabled)` now works on Android too, matching iOS. The native Android SDK (4.2.0) reads the CMP's standard `IABTCF_*` keys from the default SharedPreferences and applies the same Google TCF purpose mapping; anything set explicitly with `setConsent` still wins, per signal. Bumped the native Android SDK to `io.linkrunner:android-sdk:4.2.0`.

## 4.2.0

- Added `enableTCFConsentCollection(enabled)`, which derives the Google Ads consent state from an IAB TCF v2.2/v2.3 Consent Management Platform instead of requiring `setConsent`. The native SDK reads the CMP's standard `IABTCF_*` keys and applies Google's published purpose mapping. Anything you set explicitly with `setConsent` still wins, per signal.
- It is opt-in rather than automatic because interpreting a TC string on your behalf is a legal judgement. Only enable it if you use a TCF-compliant CMP: custom consent screens and Firebase Consent Mode do not write those keys.
- Not persisted, so call it on every launch before `init`. iOS only, matching the React Native SDK: the native Android SDK has no TCF support, so on Android the call logs and returns rather than failing on a missing method channel handler.

## 4.1.0

- Added support for Google Integrated Conversion Measurement (ICM) on iOS. The native SDK fetches Google's On-Device Measurement value during initialization and forwards it to Linkrunner. There is no Dart API to call, but ICM is **opt-in**: it stays inactive until you add Google's SDK to your app (see below). ICM is iOS-only for now; Android is unaffected.
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'io.linkrunner:android-sdk:4.1.0'
implementation 'io.linkrunner:android-sdk:4.2.0'
}
}

18 changes: 18 additions & 0 deletions android/src/main/kotlin/io/linkrunner/flutter/LinkrunnerPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ class LinkrunnerPlugin: FlutterPlugin, MethodCallHandler {
"setConsent" -> {
setConsent(call, result)
}
"enableTCFConsentCollection" -> {
val enabled = call.argument<Boolean>("enabled")
if (enabled != null) {
enableTCFConsentCollection(enabled, result)
} else {
result.error("INVALID_ARGUMENT", "enabled parameter is required", null)
}
}
"handleDeeplink" -> {
val deeplinkUrl = call.argument<String>("deeplinkUrl")
if (deeplinkUrl != null) {
Expand Down Expand Up @@ -547,6 +555,16 @@ class LinkrunnerPlugin: FlutterPlugin, MethodCallHandler {
}
}

private fun enableTCFConsentCollection(enabled: Boolean, result: Result) {
try {
NativeLinkRunner.getInstance().enableTCFConsentCollection(enabled)
android.util.Log.d("LinkRunner", "TCF consent collection ${if (enabled) "enabled" else "disabled"}")
result.success(null)
} catch (e: Exception) {
result.error("ENABLE_TCF_CONSENT_COLLECTION_FAILED", e.message, null)
}
}

private fun isAaidCollectionDisabled(result: Result) {
try {
val isDisabled = NativeLinkRunner.getInstance().isAaidCollectionDisabled()
Expand Down
17 changes: 16 additions & 1 deletion ios/Classes/SwiftLinkrunnerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ public class SwiftLinkrunnerPlugin: NSObject, FlutterPlugin {
let args = call.arguments as? [String: Any]
setConsent(args: args ?? [:], result: result)

case "enableTCFConsentCollection":
if let args = call.arguments as? [String: Any],
let enabled = args["enabled"] as? Bool {
enableTCFConsentCollection(enabled: enabled, result: result)
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "enabled parameter is required", details: nil))
}

case "setPushToken":
if let args = call.arguments as? [String: Any],
let pushToken = args["pushToken"] as? String {
Expand Down Expand Up @@ -384,7 +392,14 @@ public class SwiftLinkrunnerPlugin: NSObject, FlutterPlugin {
LinkrunnerSDK.shared.setConsent(consent)
result(nil)
}


/// Not gated on `isInitialized`: the Dart layer calls this before `init` so the first
/// payload already carries the CMP's values, and the native SDK reads the keys lazily.
private func enableTCFConsentCollection(enabled: Bool, result: @escaping FlutterResult) {
LinkrunnerSDK.shared.enableTCFConsentCollection(enabled)
result(nil)
}

private func setPushToken(pushToken: String, result: @escaping FlutterResult) {
guard isInitialized else {
result(FlutterError(code: "NOT_INITIALIZED", message: "SDK not initialized", details: nil))
Expand Down
2 changes: 1 addition & 1 deletion ios/linkrunner.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Pod::Spec.new do |s|
s.name = 'linkrunner'
# Keep in step with pubspec.yaml — these drifted apart previously (pubspec 4.0.1
# against podspec 3.4.0).
s.version = '4.1.0'
s.version = '4.3.0'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

pod spec cat LinkrunnerKit 4.1.0
rg -n "enableTCFConsentCollection|LinkrunnerKit" ios

Repository: RathodDarshil/linkrunner

Length of output: 203


🌐 Web query:

CocoaPods LinkrunnerKit 4.1.0 enableTCFConsentCollection

💡 Result:

There is no documented function or parameter named enableTCFConsentCollection for LinkrunnerKit 4.1.0 or any other version of the Linkrunner SDK. Official documentation for LinkrunnerKit [1][2] and its related SDK-less integrations [3][4][5] indicates that data collection preferences—specifically regarding iOS identifiers—are handled through different mechanisms: 1. IDFA Collection: You can control IDFA collection using the disableIdfa boolean flag during initialization [6][1]. 2. Apple Tracking Transparency: The SDK relies on standard Apple AppTrackingTransparency (ATT) protocols. To collect the IDFA, the app must include the NSUserTrackingUsageDescription key in its Info.plist and explicitly request tracking authorization from the user using ATTrackingManager.requestTrackingAuthorization() when the app is active [3][4]. 3. Android AAID Collection: For Android, there is a setDisableAaidCollection method to manage Google Advertising ID collection [7]. If you are seeing a reference to enableTCFConsentCollection in a specific codebase, it may be a custom internal implementation, a misunderstood parameter, or related to a third-party wrapper not present in the official Linkrunner SDK documentation.

Citations:


🌐 Web query:

"CocoaPods" "LinkrunnerKit" "4.1.0" "enableTCFConsentCollection"

💡 Result:

There is no evidence that the method enableTCFConsentCollection exists in the LinkrunnerKit CocoaPods library for iOS. Documentation for LinkrunnerKit does not list this method [1][2][3]. The specific term enableTCFDataCollection (often associated with TCF v2.2 compliance) appears in the documentation for other SDKs, such as AppsFlyer [4], but it is not part of the documented public API for LinkrunnerKit [1][2]. Linkrunner handles iOS privacy primarily through App Tracking Transparency (ATT) and IDFA management [5][6][7]. If you are looking to manage consent or privacy settings in LinkrunnerKit, the standard approach involves: 1. Requesting user permission via App Tracking Transparency (ATT) using requestTrackingAuthorization() [5]. 2. Providing the NSUserTrackingUsageDescription key in your Info.plist [5][6][8]. If you encountered a reference to enableTCFConsentCollection in a different SDK or a specific configuration, please verify the library name, as it does not match the current LinkrunnerKit implementation [1][2].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '## Files matching linkrunner.podspec\n'
find . -path '*linkrunner.podspec' -o -path '*SwiftLinkrunnerPlugin.swift' | sort

printf '\n## podspec\n'
sed -n '1,80p' ios/linkrunner.podspec 2>/dev/null || true

printf '\n## SwiftLinkrunnerPlugin.swift references\n'
file="$(find . -path '*SwiftLinkrunnerPlugin.swift' | head -n 1 || true)"
if [ -n "$file" ]; then
  ast-grep outline "$file" 2>/dev/null | sed -n '1,80p' || true
  rg -n "enableTCFConsentCollection|LinkrunnerKit|linkrunner" "$file" -C 3
fi

printf '\n## Podfile/podspec dependency references\n'
rg -n "LinkrunnerKit|linkrunner|enableTCFConsentCollection" .

Repository: RathodDarshil/linkrunner

Length of output: 15120


🌐 Web query:

site:docs.linkrunner.io/sdk ios SDK enableTCFConsentCollection LinkRunner

💡 Result:

The Linkrunner iOS SDK does not feature a method named enableTCFConsentCollection [1][2]. Linkrunner's current iOS privacy and data collection strategy primarily focuses on managing IDFA collection via Apple's App Tracking Transparency (ATT) framework rather than TCF (Transparency and Consent Framework) consent signal integration [1][3][2]. To control data collection on iOS in the Linkrunner SDK: 1. IDFA Collection: You can control IDFA collection during SDK initialization using the disableIdfa boolean flag [1][4][5][6]. Setting this to true disables IDFA collection for iOS devices [1][4]. 2. ATT Framework: For legitimate IDFA collection, you must include the NSUserTrackingUsageDescription in your Info.plist and use Apple's ATT framework to request user permission [3][2]. 3. Other Privacy Controls: While Android and Flutter SDKs provide explicit methods like setDisableAaidCollection to manage Google Advertising ID (AAID) [7][8], the iOS SDK relies on standard native privacy practices and the aforementioned IDFA flag to manage device identifiers [1][2]. If your implementation specifically requires TCF support, ensure you are referencing the correct SDK documentation or verify if you are conflating Linkrunner with another analytics or attribution provider that supports IAB TCF standards.

Citations:


🌐 Web query:

"enableTCFConsentCollection" "LinkRunnerSDK.shared"

💡 Result:

There is no method named enableTCFConsentCollection available in the LinkRunner SDK. Based on the official LinkRunner SDK documentation for iOS, Android, React Native, and Unity, the SDK does not provide a built-in method with that specific name [1][2][3][4][5][6]. The LinkRunner SDK focuses on attribution, analytics, and user identification, and it allows for certain data collection preferences to be managed during initialization, such as disabling IDFA on iOS or AAID on Android [1][2][3][6]. If you are looking to integrate TCF (Transparency & Consent Framework) data with mobile SDKs, this is typically handled by: 1. Using a Consent Management Platform (CMP) to obtain user consent and store the TC string in the device's local storage (e.g., UserDefaults on iOS or SharedPreferences on Android) [7][8]. 2. Some third-party SDKs have specific methods to enable TCF data collection (for example, AppsFlyer uses enableTCFDataCollection) [7], but this is not a feature of the LinkRunner SDK. If your objective is to ensure compliance when using LinkRunner, you should ensure that your app's consent flow properly records the user's choices and that you are using the SDK in accordance with your organization's privacy policy and data processing agreements [9]. If you have specific requirements for TCF support, it is recommended to contact LinkRunner support directly to verify if there are undocumented ways to pass this data or if it is currently not supported.

Citations:


🌐 Web query:

"enableTCFConsentCollection" "LinkRunnerSDK.shared" site:github.com/RathodDarshil/linkrunner_flutter

💡 Result:

There is no evidence in the provided documentation that the LinkRunner SDK contains a method or property named enableTCFConsentCollection. The LinkRunner iOS SDK functions are accessed through LinkrunnerSDK.shared, but the available documentation for this interface lists methods for initialization, attribution data, user identification, event tracking, payment processing, and deep linking, with no mention of TCF (Transparency and Consent Framework) consent collection [1]. While the Android and Flutter SDKs provide methods to disable Google Advertising ID (AAID) collection [2][3] and the iOS platform allows for tracking authorization requests via ATTrackingManager [4], the provided sources do not document a specific TCF-related configuration method for LinkRunner SDKs.

Citations:


🌐 Web query:

"enableTCFConsentCollection" "LinkRunnerSDK.shared" site:github.com/RathodDarshil/linkrunner

💡 Result:

I couldn’t find any GitHub result in RathodDarshil/linkrunner for enableTCFConsentCollection or LinkRunnerSDK.shared. The closest Linkrunner docs I found mention TCF/consent-related setup in general, but not that exact API name. [1][2]

[1] (docs.linkrunner.io)
[2] (docs.linkrunner.io)

Citations:


Keep the iOS SDK dependency update in sync.

ios/linkrunner.podspec now uses 4.3.0 but still pins LinkrunnerKit to 4.1.0, while SwiftLinkrunnerPlugin.swift calls LinkrunnerSDK.shared.enableTCFConsentCollection(enabled). Upgrade the pinned SDK to the version that actually exports this API or remove/replace this bridge if the release note about iOS TCF consent support did not land.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/linkrunner.podspec` at line 5, Update the LinkrunnerKit dependency in
ios/linkrunner.podspec to the SDK version that exports
enableTCFConsentCollection, keeping it compatible with the 4.3.0 release and the
SwiftLinkrunnerPlugin.swift bridge call; if that API is not included, remove or
replace the bridge usage instead.

s.summary = 'Flutter Package for linkrunner, track every click, download and dropoff for your app links'
s.description = <<-DESC
Flutter Package for linkrunner.io - Advanced app attribution and link tracking service.
Expand Down
39 changes: 38 additions & 1 deletion lib/linkrunner.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:developer' as developer;

import 'package:flutter/foundation.dart'
show TargetPlatform, defaultTargetPlatform;
import 'package:linkrunner/models/attribution_data.dart';
import 'package:linkrunner/models/lr_capture_payment.dart';
import 'package:linkrunner/models/lr_consent.dart';
Expand All @@ -13,7 +15,7 @@ import 'models/lr_user_data.dart';
class LinkRunner {
static final LinkRunner _singleton = LinkRunner._internal();

final String packageVersion = '4.1.0';
final String packageVersion = '4.3.0';

String? token;

Expand Down Expand Up @@ -303,6 +305,41 @@ class LinkRunner {
}
}

/// Collect the Google Ads consent state from an IAB TCF v2.2/v2.3 Consent Management
/// Platform instead of setting it yourself.
///
/// When enabled, the SDK reads the CMP's standard `IABTCF_*` keys. Anything set
/// explicitly through [setConsent] still wins, per signal.
///
/// Opt in rather than automatic, because interpreting a TC string on your behalf is a
/// legal judgement. Only enable it if you use a TCF-compliant CMP: custom consent
/// screens and Firebase Consent Mode do not write those keys.
///
/// Not persisted, so call it on every launch before [init]. Supported on iOS and
/// Android. On other platforms this is a no-op.
///
/// - Parameter enabled: Whether TCF consent collection should be enabled (default: true)
Future<void> enableTCFConsentCollection([bool enabled = true]) async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.android) {
developer.log(
'Linkrunner: TCF consent collection is only supported on iOS and Android, ignoring',
name: packageName,
);
return;
}

try {
await LinkRunnerNativeBridge.enableTCFConsentCollection(enabled);
} catch (e) {
developer.log(
'Linkrunner: Failed to ${enabled ? 'enable' : 'disable'} TCF consent collection',
name: packageName,
error: e,
);
}
}

/// Disable AAID (Google Advertising ID) collection on Android
/// When disabled, the SDK will not collect or send the Google Advertising ID (GAID).
/// This is useful for apps targeting children or families to comply with Google Play's Family Policy.
Expand Down
19 changes: 19 additions & 0 deletions lib/linkrunner_native_bridge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,25 @@ class LinkRunnerNativeBridge {
}
}

/// Collect the Google Ads consent state from an IAB TCF Consent Management Platform.
/// Supported on iOS and Android.
static Future<void> enableTCFConsentCollection(bool enabled) async {
try {
await _channel.invokeMethod('enableTCFConsentCollection', {
'enabled': enabled,
});
developer.log(
'TCF consent collection ${enabled ? 'enabled' : 'disabled'} successfully',
name: packageName);
} on PlatformException catch (e) {
developer.log(
'Failed to ${enabled ? 'enable' : 'disable'} TCF consent collection: ${e.message}',
error: e,
name: packageName);
rethrow;
}
}

/// Check if AAID collection is currently disabled (Android only)
/// Returns true if AAID collection is disabled, false otherwise
static Future<bool> isAaidCollectionDisabled() async {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: linkrunner
description: "Flutter Package for linkrunner, track every click, download and dropoff for your app links"
version: 4.1.0
version: 4.3.0
homepage: "https://www.linkrunner.io/"
repository: "https://github.com/RathodDarshil/linkrunner_flutter"
documentation: "https://github.com/RathodDarshil/linkrunner_flutter#getting-started"
Expand Down
74 changes: 74 additions & 0 deletions test/tcf_consent_collection_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:linkrunner/linkrunner.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

const channel = MethodChannel('linkrunner_native');
final calls = <MethodCall>[];

setUp(() {
calls.clear();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
calls.add(call);
return null;
});
});

tearDown(() {
debugDefaultTargetPlatformOverride = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
});

group('enableTCFConsentCollection', () {
test('forwards the flag to the native SDK on iOS', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

await LinkRunner().enableTCFConsentCollection(true);

expect(calls, hasLength(1));
expect(calls.single.method, 'enableTCFConsentCollection');
expect(calls.single.arguments, {'enabled': true});
});

test('defaults to enabling collection', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

await LinkRunner().enableTCFConsentCollection();

expect(calls.single.arguments, {'enabled': true});
});

test('forwards a disable call', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

await LinkRunner().enableTCFConsentCollection(false);

expect(calls.single.arguments, {'enabled': false});
});

// The native Android SDK supports TCF from 4.2.0, so Android goes through the
// same method channel handler as iOS.
test('forwards the flag to the native SDK on Android', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;

await LinkRunner().enableTCFConsentCollection(true);

expect(calls, hasLength(1));
expect(calls.single.method, 'enableTCFConsentCollection');
expect(calls.single.arguments, {'enabled': true});
});

test('is a no-op on other platforms', () async {
debugDefaultTargetPlatformOverride = TargetPlatform.macOS;

await LinkRunner().enableTCFConsentCollection(true);

expect(calls, isEmpty);
});
});
}