Skip to content

Dev/delivery 124647/rpc implementation - #425

Merged
af-obodovskyi merged 64 commits into
developmentfrom
dev/DELIVERY-124647/rpc-implementation
Sep 3, 2026
Merged

Dev/delivery 124647/rpc implementation#425
af-obodovskyi merged 64 commits into
developmentfrom
dev/DELIVERY-124647/rpc-implementation

Conversation

@af-obodovskyi

Copy link
Copy Markdown

Summary

Migrates the AppsFlyer Unity plugin's core API from the legacy AndroidJavaClass/DllImport native bridges to a unified, schema-driven RPC transport. Every public method and parameter is now aligned to a canonical JSON schema (appsflyer-plugins-rpc-schema.json) shared across platforms, replacing ad-hoc per-platform bridging code with a single dispatch path (AppsFlyerRPCClient → Fire/Query).

What changed

C# plugin API (Assets/AppsFlyer/AppsFlyer.cs)

  • Every public method now routes through the RPC transport instead of AndroidJavaClass/P/Invoke calls; removed the old native bridge interfaces (IAppsFlyerAndroidBridge, IAppsFlyerIOSBridge, IAppsFlyerNativeBridge) and platform delegation classes (AppsFlyerAndroid.cs, AppsFlyeriOS.cs).
  • Fixed a set of schema-alignment bugs found during a Unity-vs-schema-vs-RN audit:
    • isSessionReady() now performs a live RPC query instead of returning a stale client-cached flag.
    • enableDebug/setDisableNetworkData parameter names renamed to match the schema's canonical public contract (enabled / isDisable).
    • setLogLevel now accepts case-insensitive input (schema's canonical values are lowercase) and uppercases before firing the wire RPC.
    • generateInviteLink now remaps the canonical referrerCustomerId key to Android's wire key customerId internally, instead of requiring callers to know the platform-specific key.
    • updateServerUninstallToken (iOS) now hex-encodes the device token instead of Base64, matching the schema's required wire format.

Android native bridge

  • Migrated AppsFlyerRPCBridge and PurchaseRevenueBridge from Java to Kotlin.
  • Dependency/gradle updates: added play-services-ads-identifier (fixes a ClassNotFoundException reading GAID), and temporarily disabled purchase-connector due to an AGP8 manifest-namespace conflict with af-android-sdk (needs an upstream repackage before re-enabling).

iOS native bridge

  • Added a Swift RPC wrapper (AppsFlyerRPCWrapper.swift) replacing the old Obj-C++ RPC wrapper.

Sample/test app

  • Added AppsFlyerAPITester.cs — a UI-driven manual test harness covering the full RPC API surface.
  • Added ATTPermissionRequest.mm for iOS App Tracking Transparency prompts; removed the old AppsFlyerOpenURL.mm.
  • Updated Android manifest/gradle templates and QATestScript.cs for the new bridge.

Removed

  • Stale prebuilt binaries that shouldn't have been in source control: the macOS bundle (test-app/Assets/AppsFlyer/Mac/...) and unitywrapper.aar.

Out of scope / explicitly excluded

  • appsflyer-plugins-rpc-schema.json is intentionally not included in this MR.
  • handleOpenUrl, handleLaunchOptions, continueUserActivity were audited against the schema (which flags them internal-only) but are kept public and unchanged — they're documented, tested, and tied to the native AppsFlyerAttribution handling; removing them would be a breaking change with no clear benefit.
  • Build/run-generated artifacts (Xcode export, APKs, Editor-version churn, EDM4U-resolver-generated settings, backup files) were kept out of version control.

Testing

  • Assets/AppsFlyer/Tests/Tests_Suite.cs updated for the new RPC-layer assertions.
  • Manual verification recommended via the new AppsFlyerAPITester harness in the sample app on both platforms, particularly: isSessionReady (live query behavior), generateInviteLink (Android key remap), and updateServerUninstallToken (iOS hex encoding).
  • No native SDK behavior changes required — confirmed via RPC mapping docs that both Android and iOS already implement the relevant RPC methods correctly.

af-margot and others added 30 commits June 14, 2026 10:38
fix(ios-e2e): Xcode 26 + Swift compat libs for PurchaseConnector 6.17.x
Keep release branch versions for SDK/version files (6.17.9 iOS, 6.17.6 Android,
2.1.2 PC). Keep master for README/docs. Merge both CHANGELOG entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….17.x/6.17.900-rc1

Revert "Release 6.17.900"
- docs/RPC-Implementation-Plan.md: full spec covering iOS and Android RPC bridge architecture, protocol definition, and phased rollout
- plans/01-rpc-phase1-csharp-layer.md: detailed execution plan for the C# layer (AppsFlyerRPCClient, onRPCEvent handler, method routing, unit tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces per-platform native bindings with a unified JSON-RPC bridge
(AppsFlyerRPC.xcframework on iOS, af-android-plugin-bridge on Android).

Core changes:
- AppsFlyer.cs: all SDK calls routed through AppsFlyerRPCClient; platform-split
  #if blocks reordered so UNITY_ANDROID is checked first (safe — mutually exclusive
  on real devices); setCurrentDeviceLanguage guarded iOS-only; setPhoneNumber
  Android no-op (bridge requires countryCode, public API does not expose it)
- AppsFlyerRPCClient.cs: new IAppsFlyerRPCClient interface + DefaultInstance
- AppsFlyerRPCBridge.java: Android RPC bridge implementation
- AppsFlyerRPCWrapper.mm + AppsFlyerRPC.xcframework: iOS RPC bridge

Tests:
- Tests_Suite.cs: Android contract tests (6 new), iOS routing guards updated,
  platform exclusions validated; 67 tests total (61 iOS+shared, 6 Android)

Docs:
- Android-RPC-Mapping.md: plugin bridge → SDK API reference for Android
- iOS-RPC-Mapping.md: AppsFlyerRPC → AppsFlyerLib method mapping for iOS
- docs/RPC-Coverage.md: cross-platform RPC coverage matrix

E2E validated locally on emulator/simulator — zero RPC parse errors on both
platforms after fixing subscribeForDeepLink method name split.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the static xcframework from Assets/Plugins/iOS and repo root;
add pod 'AppsFlyerRPC' 7.0.11 to AppsFlyerDependencies.xml so EDM4U
resolves it from CocoaPods alongside AppsFlyerFramework.

AppsFlyerRPCWrapper.mm is unchanged — the ObjC API surface is identical.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove af-android-plugin-bridge, af-android-sdk-base, and af-android-sdk-dev
local AARs from unitywrapper/libs and test-app/Assets/Plugins/Android.

unitywrapper/build.gradle:
- implementation 'com.appsflyer:af-android-plugin-bridge:7.0.1'
- compileOnly "com.appsflyer:af-android-sdk:$ANDROID_SDK_VERSION" (replaces sdk-base/dev local files)
- removed flatDir repository

AppsFlyerDependencies.xml:
- added com.appsflyer:af-android-plugin-bridge:7.0.1 so EDM4U declares it
  for Unity consumers alongside af-android-sdk

Also includes unit testing examples appended to iOS-RPC-Mapping.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove EmailCryptType and setPhoneNumber: P/Invoke bridge stubs — both APIs
were dropped in AppsFlyerFramework 7.0.1; the RPC layer handles these calls.
Simplify mainTemplate.gradle to only declare af-android-plugin-bridge:7.0.1
since it provides af-android-sdk transitively (no direct SDK dep needed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace android_sdk_version/ios_sdk_version inputs with
android_plugin_bridge_version and ios_rpc_version throughout the
rc-release workflow, bump-version.sh, and ios-pod-install.sh.

- rc-release.yml: new inputs for af-android-plugin-bridge and
  AppsFlyerRPC; verify step checks RPC coords in AppsFlyerDependencies.xml;
  Slack message shows RPC bridge versions
- bump-version.sh: bumps af-android-plugin-bridge in deps XML, build.gradle,
  and mainTemplate.gradle; bumps AppsFlyerRPC in deps XML and ios-pod-install.sh;
  retains android_sdk_version for wrapper compileOnly dep
- ios-pod-install.sh: reads AppsFlyerRPC version from AppsFlyerDependencies.xml
  and writes it into the Podfile instead of AppsFlyerFramework

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…wrapper.yml

workflow_dispatch does not support a secrets: block; it inherits secrets
from the repository/environment directly. Secrets were incorrectly duplicated
under workflow_dispatch, causing an IDE validation error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
af-android-plugin-bridge:7.0.1 transitively brings af-android-sdk:7.0.1.
The compileOnly dep in gradle.properties must match; 6.17.6 was the old
direct-SDK version and is no longer correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
af-android-plugin-bridge declares af-android-sdk as api, so sdk classes
are available to the wrapper via the bridge alone. Verified by successful
assembleRelease without the compileOnly dep.

Removes ANDROID_SDK_VERSION from: gradle.properties, unitywrapper/build.gradle,
bump-version.sh, publish-android-wrapper.sh, publish-android-wrapper.yml,
and rc-release.yml. android_sdk_version is no longer an input anywhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update PLUGIN_VERSION in AppsFlyerAndroidWrapper.java and VERSION_NAME
in gradle.properties to 7.0.1 ahead of unity-wrapper Sonatype publish.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Method was missing from the wrapper causing NoSuchMethodError at runtime.
af-android-plugin-bridge routes it through the RPC bridge to AppsFlyerLib.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-version to 7.0.11 to include the setPartnerData fix missing from 7.0.1.
Also updates AppsFlyerDependencies.xml to reference unity-wrapper:7.0.11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PurchaseConnector 7.0.0 pinned AppsFlyerFramework = 7.0.0, conflicting
with AppsFlyerRPC 7.0.11 which requires AppsFlyerFramework = 7.0.1.
Bumped PurchaseConnector to 7.0.1 (compatible with AppsFlyerFramework 7.0.1)
and AppsFlyerFramework to 7.0.1. Also removed af-android-sdk:6.17.6 explicit
declaration — it is a transitive dep via af-android-plugin-bridge:7.0.1 (api).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… billing v8

- purchase-connector 2.1.2 → 2.2.0 (billing library v8 support)
- billingclient:billing 5.2.0 → 8.0.0
- unity-wrapper artifact version 7.0.11 → 7.0.12
- Remove af-android-sdk:6.17.6 explicit declaration (transitive via bridge)
- AppsFlyerFramework 7.0.0 → 7.0.1, PurchaseConnector 7.0.0 → 7.0.1 in iOS pods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nvoke

AppsFlyerRPC 7.0.11 does not handle registerDeeplinkListener, so the deep
link delegate was never set and onDeepLinking callbacks never fired. Fall
back to instance.subscribeForDeepLink (P/Invoke _subscribeForDeepLink) on
iOS which correctly sets AppsFlyerLib.deepLinkDelegate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…own SDK lifecycle

AppsFlyerAndroid.initSDK called AppsFlyerAndroidWrapper.initSDK (which called
AppsFlyerLib.init with a conversion listener) AND then AppsFlyer.cs fired
ExecuteFire("init") through the RPC bridge, which called AppsFlyerLib.init again
overwriting the conversion listener. Result: onConversionDataFail("Launch exception: null").

Fix: AppsFlyerAndroid.initSDK now only wires the RPC bridge callback routing
(InitAndroidBridge). AppsFlyerLib.init is called exclusively by the RPC bridge.

Similarly, startSDK no longer calls instance.startSDK on Android — the RPC
bridge owns AppsFlyerLib.start. iOS keeps both paths (deprecated _startSDK
is a no-op on the native side).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
startSDK no longer calls instance.startSDK() on Android (removed to fix
double AppsFlyerLib.init). The shared test must verify the RPC path
(ExecuteFire("start")) which fires on all platforms, not the iOS-only
native bridge call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nSessionReady via RPC

Both iOS and Android were firing onSessionReady synthetically instead of waiting
for the native SDK callback. Collapsed to a single ExecuteFire call on both
platforms; removed dead _nativeRegisterSessionReadyListener P/Invoke on iOS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Required by rc-release.yml validation: grep -q "kAppsFlyerPluginVersion = \"$PLUGIN_VERSION\"".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getConversionData() fires ExecuteFire("registerConversionListener") which is
required for onInstallConversionData to reach Unity. E2E phase_1 was failing
because the listener was never registered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread Assets/AppsFlyer/AppsFlyer.cs Outdated
/// but this is no longer a blocking unknown.
/// </summary>
public static event EventHandler OnRequestResponse
public static async Awaitable<AFSDKValidateAndLogResult> validateAndLogInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> additionalParameters)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

validateAndLogInAppPurchase is split into two method overloads distinguished only by parameter type — (AFPurchaseDetailsAndroid details, ...) (line 960) and (AFSDKPurchaseDetailsIOS details, ...) (line 982) — each independently gated by its own #if UNITY_ANDROID / #if UNITY_IOS || UNITY_STANDALONE_OSX block, and each returning null on the "wrong" platform. This is overloading standing in for what should be a single abstraction: AFPurchaseDetailsAndroid and AFSDKPurchaseDetailsIOS describe the same domain concept (a purchase to validate) but share no common type, so adding a third platform means adding a third overload rather than a third implementation of an existing contract — a SOLID (Open/Closed) violation. AFSDKValidateAndLogResult (the shared return type both overloads already converge on via QueryValidateAndLogAsync) should likewise be defined against an interface rather than as a single concrete class, so the result contract is explicit and not just an accident of both overloads happening to call the same private helper today.

Recommend: introduce a common purchase-details interface (e.g. IAFPurchaseDetails) implemented by AFPurchaseDetailsAndroid and AFSDKPurchaseDetailsIOS, and collapse the two overloads into one validateAndLogInAppPurchase(IAFPurchaseDetails details, Dictionary<string, string> additionalParameters) that dispatches to the platform-specific payload-building logic internally (or via a small per-platform strategy), rather than via overload resolution on the caller's static type. Define AFSDKValidateAndLogResult against a corresponding result interface for the same reason.

References:

af-obodovskyi and others added 5 commits August 31, 2026 18:01
…eak, PII log redaction, purchase-validation interface refactor

- FireAsync now catches/logs RPC exceptions so non-awaited callers aren't silently swallowed
- Fix stale getAppsFlyerUID doc comment (matches isSessionReady/getSdkVersion wording)
- unregisterConversionListener/unregisterDeeplinkListener null out static callback fields
- Rename generateInviteLinkAsync -> generateInviteLink; catch AppsFlyerRPCException before generic Exception
- AppsFlyerRPCBridge.kt: stop logging full RPC request payload (PII/revenue data) on dropped fire-and-forget calls
- Collapse validateAndLogInAppPurchase Android/iOS overloads into one method via new IAFPurchaseDetails/IAFValidateAndLogResult interfaces

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lowing

- test-app: add AppsFlyerLifecycleNudgeActivity, a momentary translucent
  Activity that forces a synthetic pause/resume on AppsFlyerUnityActivity
  right after registerSessionReadyListener(). AppsFlyerLib's own
  ActivityLifecycleCallbacks are only registered once Unity's managed layer
  boots (via init()), which is always a beat behind Android's real,
  launch-triggering onResume() - without this nudge, session readiness/
  start() would stay unevaluated until the user genuinely backgrounds and
  foregrounds the app. No AppsFlyerLib/RPC API is called by the nudge
  itself; iOS is unaffected (its SDK re-evaluates readiness immediately if
  already active at listener-registration time).
- AppsFlyer.cs: FireAsync now rethrows after logging, so awaited callers
  still see RPC failures instead of having them silently swallowed
  alongside the non-awaited-caller logging fix from the prior review pass.
- AppsFlyerDependencies.xml: drop the unity-wrapper Maven coordinate (no
  published version matches this repo's current Kotlin
  AppsFlyerRPCBridge - every published version calls
  AppsFlyerRpcHandler's constructor with a raw Context where
  af-android-plugin-bridge:7.0.12 now expects a Function0<Context>
  supplier); add iOS Swift Package Manager entries for
  AppsFlyerLib-Dynamic/AppsFlyerRPC/PurchaseConnector-Dynamic.
- test-app: vendor a locally-built unity-wrapper .aar directly under
  Assets/Plugins/Android pending a published fix, and sync
  mainTemplate.gradle to match.
- Add missing .meta files for IAFPurchaseDetails.cs/IAFValidateAndLogResult.cs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ady API

- README/Installation: require Unity 2023.1+ (raised from 2019.4, for
  Awaitable/Awaitable<T>) and EDM4U 1.2.187+ (earlier 1.2.x mis-resolves
  the new iOS Swift Package Manager AppsFlyerRPC dependency).
  Add a breaking-changes table for the 7.x RPC-bridge rename/async
  migration (initSDK/startSDK/stopSDK/isSDKStopped/getAppsFlyerId ->
  init/start/stop/isStoppedAsync/getAppsFlyerUIDAsync).
- API.md/BasicIntegration.md: bring in line with the current AppsFlyer.cs
  surface (Awaitable *Async twins, no more initSDK/startSDK), and add a
  Session Ready Listener section covering registerSessionReadyListener(),
  OnSessionReady, isSessionReady()/Async(), and the Android cold-launch
  caveat addressed by the lifecycle-nudge fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Earlier 1.2.x releases (e.g. 1.2.183) mis-resolve this plugin's iOS Swift
Package Manager dependencies (AppsFlyerRPC, introduced with the RPC bridge
migration) - see docs/Installation.md#requirements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EDM4U links AppsFlyerLib-Dynamic/PurchaseConnector-Dynamic into UnityFramework's
Frameworks phase but never embeds the resulting dynamic frameworks into the app
bundle, causing a dyld "Library not loaded" crash at launch. Embeds them by their
real framework binary name via the public PBXProject API, the same way Unity
embeds UnityFramework.framework/UnityRuntime.framework. AppsFlyerRPC is a static
xcframework and needs neither linking help nor embedding.

In the test app, wire up the existing (but previously unused) native ATT prompt
and fix its ordering relative to start(): the SDK's first session send was firing
before the user answered the tracking prompt, so it could never carry IDFA even on
a grant. start() now waits for the native ATT completion callback (with a timeout
fallback), replacing waitForATTUserAuthorizationWithTimeoutInterval, which no
longer exists in this plugin version.
Comment thread Assets/AppsFlyer/AppsFlyer.cs Outdated
/// <param name="eventName">Event Name as String.</param>
/// <param name="eventValues">Event Values as Dictionary.</param>
public static void sendEvent(string eventName, Dictionary<string, string> eventValues)
/// <summary>Starts the SDK. A session is sent immediately, and on every foreground transition.</summary>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shipping both a synchronous and an *Async twin for the same underlying RPC call (isSessionReady()/isSessionReadyAsync() here, and the same pattern repeated for getSdkVersion, getAppsFlyerUID, getOutOfStore, isPreInstalledApp, getAttributionId, getHostName, getHostPrefix, isStopped) is redundant API surface — every one of these pairs hits the same native call, just via Query (blocking) vs QueryAsync (background-thread hop). Recommend converging on a single pattern without an Async suffix in the method name (i.e. every getter is simply awaitable), rather than maintaining two names, two doc comments, and two sets of tests per getter going forward.

{
if (text.Contains("/* " + frameworkFileName + " in Embed Frameworks */")) continue;

string fileGuid = proj.AddFile(frameworkFileName, "Frameworks/" + frameworkFileName, PBXSourceTree.Build);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the PR's headline iOS fix (resolving a "Library not loaded" dyld crash by embedding AppsFlyerLib.framework/PurchaseConnector.framework into the app bundle), but the embed step references the file at "Frameworks/" + frameworkFileName relative to BUILT_PRODUCTS_DIR. The surrounding comment justifies this by analogy to how Unity embeds UnityFramework.framework — but that framework is a genuine Xcode target product built directly into BUILT_PRODUCTS_DIR, whereas AppsFlyerLib.framework/PurchaseConnector.framework here are Swift Package Manager package library products consumed by the main target. Xcode's build system places package-library framework products under BUILT_PRODUCTS_DIR/PackageFrameworks/, not BUILT_PRODUCTS_DIR/Frameworks/ — a well-documented SPM/Xcode integration gotcha (see the referenced Firebase issue). If that's the case here, this embed step silently references a file that doesn't exist at the assumed path, and the exact crash this PR sets out to fix would persist (or the build fails outright with a missing-file error).

This needs to be verified against an actual Xcode build (inspect the generated project's DerivedData/.../Build/Products/<config>-iphoneos/ output) before merge — it's the difference between this PR's central fix working or not.

References:

PBXProject proj = new PBXProject();
proj.ReadFromFile(projPath);
string mainTarget = proj.GetUnityMainTargetGuid();
proj.AddCopyFilesBuildPhaseBeforeTargetPostprocess(mainTarget, "Embed Frameworks", "", "10");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AddCopyFilesBuildPhaseBeforeTargetPostprocess(mainTarget, "Embed Frameworks", "", "10") unconditionally creates a new copy-files build phase in memory every run, before the per-file idempotency check (the raw-text scan a few lines below) has a chance to decide anything. It's currently masked because the write (proj.WriteToFile) is skipped whenever changed stays false, but there's no lookup-by-name for an existing "Embed Frameworks" phase — on any build where at least one framework still needs embedding (first build, or a project regenerated fresh), and on any future build with partially-persisted state from an earlier incremental build, this can end up persisting a second, duplicate "Embed Frameworks" phase. Two same-named copy-files phases targeting overlapping content is a known source of Xcode "Multiple commands produce..." build failures.

Recommend looking up an existing "Embed Frameworks" PBXCopyFilesBuildPhase by name first (via proj.GetAllBuildPhasesForTarget or similar) and reusing it, rather than assuming none exists.

References:

Comment thread docs/BasicIntegration.md Outdated
2. Scroll down and select `Privacy - Tracking Usage Description`.
3. Add as the value the wording you want to present to the user when asking for permission to collect the IDFA.
3. Call the `waitForATTUserAuthorizationWithTimeoutInterval` api before `startSDK()`
3. Call the `waitForATTUserAuthorizationWithTimeoutInterval` api before `start()`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This step (and its code sample at line 139 calling AppsFlyer.waitForATTUserAuthorizationWithTimeoutInterval(60)) documents an API that no longer exists — confirmed via Assets/AppsFlyer/Tests/Tests_Suite.cs:566, which asserts typeof(AppsFlyer).GetMethod("waitForATTUserAuthorizationWithTimeoutInterval") is null with the comment "confirmed out of scope." The sample app's own new code (QATestScript.cs:164) says outright: "This replaces waitForATTUserAuthorizationWithTimeoutInterval, which no longer exists in this plugin version... despite still being documented." A developer following this guide gets a compile error. docs/API.md:1494-1507 has the identical stale entry, and the removal isn't listed in CHANGELOG.md's breaking-changes bullet either.

Recommend replacing this step with guidance to request ATT authorization directly (e.g. via a small native call) and pointing to QATestScript.cs's RequestATTThenStart() as a reference implementation, removing the stale docs/API.md entry, and adding the removal to CHANGELOG.md's breaking-changes list.

References:

Comment thread docs/API.md

### isSDKStopped
**`bool isSDKStopped()`**
### isStopped

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This section documents only isStoppedAsync(), omitting the synchronous isStopped() twin that every other getter on this page documents alongside its *Async counterpart. More importantly, it omits a real platform limitation visible in the implementation's own doc comment (AppsFlyer.cs:888-889): there is no iOS RPC method for isStopped at all, so on iOS this always returns false regardless of actual SDK state — a silent wrong-answer risk for any integrator who checks this on iOS expecting it to reflect stop().

Note: AppsFlyer.cs:134 (checked, pending resolution) recommends collapsing the isStopped()/isStoppedAsync() sync/*Async duplication — and the same pattern across getSdkVersion, getAppsFlyerUID, isSessionReady, getOutOfStore, isPreInstalledApp, getAttributionId, getHostName, getHostPrefix — down to a single awaitable method per getter. If that's adopted, this doc fix applies to whichever single method survives rather than to an *Async-suffixed twin specifically; the missing-sync-signature half of this finding becomes moot, but the iOS-only caveat still needs documenting either way.

Recommend documenting both signatures together for now (matching the page's own convention) and adding an explicit "Android only — always returns false on iOS" note, revisiting once the sync/async consolidation in AppsFlyer.cs:134 is decided.

References:

@pazlavi

pazlavi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Inline comment on Assets/AppsFlyer/AppsFlyerConsent.cs:60 (line not in diff — posted as general comment)

ForGDPRUser(...)/ForNonGDPRUser() are marked [Obsolete(...)] but still present and still what docs/BasicIntegration.md's "Manually collect consent data" examples (lines 220, 233) teach integrators to use, instead of the recommended new AppsFlyerConsent(isUserSubjectToGDPR:, hasConsentForDataUsage:, hasConsentForAdsPersonalization:, hasConsentForAdStorage:) constructor. This plugin version is already a major, breaking release — CHANGELOG.md's v7.0.1 entry states plainly "No backward-compatible aliases" for the other renamed APIs (initSDKinit, startSDKstart, etc.) — so keeping these two obsolete-but-functional methods around is inconsistent with that policy: a genuinely deprecated, superseded API should be removed now rather than carried forward with a warning into the next major version.

Recommend removing ForGDPRUser(...) and ForNonGDPRUser() from AppsFlyerConsent.cs in this release, updating both docs/BasicIntegration.md consent examples to use new AppsFlyerConsent(...) instead, and adding the removal to CHANGELOG.md's breaking-changes list alongside the other API renames.

References:

af-obodovskyi and others added 14 commits September 2, 2026 11:41
…ation

Addresses PR review comment: isStopped section was missing the sync
signature (unlike every other getter on the page) and the platform
limitation already noted in AppsFlyer.cs's own doc comment.
…ethods

Consistent with this major release's no-backward-compatible-aliases
policy for the other renamed APIs. Docs and the QA test app now use
the new AppsFlyerConsent(...) constructor directly.
…erences

That API was removed during the RPC migration (Tests_Suite.cs:566-567
asserts it no longer exists) but BasicIntegration.md and API.md still
documented and called it, which would fail to compile. Point to
QATestScript.cs's RequestATTThenStart() as the reference implementation
instead, and add the removal to CHANGELOG.md's breaking-changes list.
…andler

handleCollectDataFromLauncherActivity casts the provided context to
Activity and errors (422) if it isn't one, so collectDataFromLauncherActivity()
was always failing on Android. Confirmed via decompiling
af-android-plugin-bridge that the handler's own cached appContext already
derives applicationContext internally before caching, so returning the
raw Activity here doesn't reintroduce the leak the prior comment was
guarding against.
The prefab (AppsFlyerObjectScript.cs, AppsFlyerObjectEditor.cs, its
logo asset) is removed in favor of manual integration only. Nothing
in the plugin itself depended on it - it was purely an optional
convenience wrapper around AppsFlyer.init()/start().

Docs updated: BasicIntegration.md's dedicated prefab section and
MacOS-initialization step are replaced with pointers to manual
integration; other docs' sample code (previously modeled on extending
AppsFlyerObjectScript) is renamed to a plain AppsFlyerInit class name
so it no longer implies the removed shipped script. Breaking change
noted in CHANGELOG.md and README.md's 7.x.x breaking-changes section.
…ample

Both receipt-validation code samples still called the pre-7.0.1
initSDK()/startSDK() APIs, which were renamed to init()/start() and
made async Awaitable with no backward-compatible alias.
…erences

These APIs were renamed to init/start/stop/getAppsFlyerUIDAsync (all
now async Awaitable) in the 7.0.1 RPC migration with no
backward-compatible alias, but several doc samples across
DMAConsent.md, InAppEvents.md, MigrationGuide.md, UnifiedDeepLink.md,
UninstallMeasurement.md, UserInvite.md, conversion-data-unity.md, and
purchase-connector.md still called the old names - copy-pasting them
would fail to compile. Left untouched: docs/API.md's 'Renamed from X'
notes, RPC-Coverage.md/RPC-Implementation-Plan.md's historical mapping
tables, and README.md/Introduction.md's own breaking-changes
announcements, since those intentionally reference the old names for
historical/migration context.
PBXProject.FindFileGuidByProjectPath and AddFileToEmbedFrameworks are
both documented as no-ops when the file/embed entry already exists,
so the idempotency guard no longer needs to scan the raw .pbxproj text
for a comment string that happened to match Xcode's current output
format.
isSessionReady(), getSdkVersion(), getAppsFlyerUID(), getOutOfStore(),
isPreInstalledApp(), getAttributionId(), getHostName(), getHostPrefix(),
and isStopped() blocked the calling thread (up to 5s on iOS native lag)
and duplicated their already-existing *Async twins. Removed the sync
forms; *Async is now the only public API for these.

Updated Tests_Suite.cs (dropped the now-obsolete sync-only tests),
docs/API.md, docs/BasicIntegration.md, README.md, and the sample app's
AppsFlyerAPITester.cs/QATestScript.cs to call the *Async APIs.
AppsFlyerAPITester's Call delegate is now Func<ApiEntry, Awaitable>,
properly awaited before logging/displaying results, since firing an
async lambda without awaiting it logged the "OK" fallback before the
real result was ever assigned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nment

Removes the sync/async duplicate getter pattern entirely and renames the
remaining *Async methods (getSdkVersion, getAppsFlyerUID, isSessionReady,
isStopped, getOutOfStore, isPreInstalledApp, getAttributionId, getHostName,
getHostPrefix) to drop the Async suffix, matching the RPC schema's canonical
names and the rest of the async-by-default public API (init/start/etc).

Updates test method names, the sample app's API tester and QA script to use
the new names, and syncs README/CHANGELOG/API/BasicIntegration/MigrationGuide
docs accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…art/logEvent

Suppresses CS1998 for AppsFlyer.cs, where many methods are #if UNITY_ANDROID/
UNITY_IOS-guarded and legitimately have no await on the other platform, and
makes the two Obsolete sharing-filter wrappers' fire-and-forget dispatch
explicit with a discard to fix CS4014.

Adds the RPC schema's awaitResponse parameter (already present on start and
logEvent) to their C# signatures, routing through the blocking Execute() path
when true so callers can wait for the actual server round trip instead of
just the fire-and-forget dispatch to native - matching the pattern already
available in the react-native/flutter plugins. Defaults to false, preserving
existing fire-and-forget behavior for all current call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BuildScript.cs: drop obsolete AndroidArchitecture.X86_64 (no longer
supported) and switch SetApplicationIdentifier(BuildTargetGroup, string) to
the non-obsolete NamedBuildTarget overload for both Android and iOS.

QATestScript.cs: suppress the CS0414 unused-field warning on
_attDetermined, which is only read inside the UNITY_IOS && !UNITY_EDITOR
branch of RequestATTThenStart() and so is genuinely write-only on Editor/
Android builds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ester

setSharingFilterForAllPartners()/setSharingFilter() are exercised
intentionally for QA coverage despite being marked Obsolete, and the local
AsAwaitable() helper genuinely has no await by design (it wraps a
synchronous void call to fit the Func<ApiEntry, Awaitable> shape). Suppress
CS0618/CS1998 at each site instead of leaving unexplained warnings in the
build log.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lter

Both were Obsolete-marked thin wrappers around setSharingFilterForPartners
with no remaining callers worth keeping around. Removes them and their sole
call sites in the sample app's API tester (along with the now-unused
AsAwaitable() helper that existed only to adapt their void signature), and
updates docs/API.md, docs/RPC-Coverage.md, and CHANGELOG.md accordingly.
Also fixes setSharingFilterForPartners' doc, which was stale (void instead
of async Awaitable).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@af-obodovskyi
af-obodovskyi changed the base branch from master to development September 3, 2026 10:02
Also fixes package.json's stale "unity": "2019.4" min-version field
(should have been 2023.1 since Awaitable support was added) and syncs
its "version" field, which had drifted to 6.17.900.
@af-obodovskyi
af-obodovskyi merged commit 0cfc553 into development Sep 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants