feat: ad tracking API#1750
Conversation
Adds public API and model types for tracking ad lifecycle events: - AdMediatorName, AdFormat, AdRevenuePrecision typed wrappers with predefined constants - AdDisplayedData, AdOpenedData, AdLoadedData, AdRevenueData, AdFailedToLoadData event data classes - 5 static methods on Purchases: trackAdDisplayed, trackAdOpened, trackAdLoaded, trackAdRevenue, trackAdFailedToLoad - Method channel tests verifying correct method names and map serialization
Wires trackAdDisplayed, trackAdOpened, trackAdRevenue, trackAdLoaded, and trackAdFailedToLoad through to RCCommonFunctionality. All methods guarded with @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0). iOS and macOS share the same .m file (hard link).
Wires up the 5 trackAd* methods on Android (CommonKt calls), adds no-op cases on web, and covers all new public API in api_tester.
tonidero
left a comment
There was a problem hiding this comment.
Looking great! Nothing too blocking, just some thoughts. Let me know what you think!
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a1b29ee. Configure here.
| void _checkProperties(AdFailedToLoadData data) { | ||
| AdMediatorName mediatorName = data.mediatorName; | ||
| AdFormat adFormat = data.adFormat; | ||
| String? placement = data.placement; | ||
| String adUnitId = data.adUnitId; | ||
| int? mediatorErrorCode = data.mediatorErrorCode; | ||
| Map<String, dynamic> map = data.toMap(); | ||
| } |
There was a problem hiding this comment.
I see we're not including String? networkName = data.networkName for this one. Is it intentional? AdDisplayedData, AdOpenedData, and AdLoadedData all have it.
There was a problem hiding this comment.
Yes, intentional. We don't have a network when we fail to load (we only know the network when one was successfully selected by the process). I've updated the PR desc.
rickvdl
left a comment
There was a problem hiding this comment.
Looks good! Two small comments, and one other thing;
I think we should add pr:feat instead of pr:other since we're adding new APIs
**This is an automatic release.** ## RevenueCat SDK ### ✨ New Features * feat: ad tracking API (#1750) via Peter Porfy (@peterporfy) ### 📦 Dependency Updates * [AUTOMATIC BUMP] Updates purchases-hybrid-common to 18.8.0 (#1760) via RevenueCat Git Bot (@RCGitBot) * [Android 10.6.1](https://github.com/RevenueCat/purchases-android/releases/tag/10.6.1) * [iOS 5.74.0](https://github.com/RevenueCat/purchases-ios/releases/tag/5.74.0) * [iOS 5.73.1](https://github.com/RevenueCat/purchases-ios/releases/tag/5.73.1) * [Web 1.41.1](https://github.com/RevenueCat/purchases-js/releases/tag/1.41.1) * [Web 1.41.0](https://github.com/RevenueCat/purchases-js/releases/tag/1.41.0) * [AUTOMATIC BUMP] Updates purchases-hybrid-common to 18.7.1 (#1758) via RevenueCat Git Bot (@RCGitBot) ### 🔄 Other Changes * Bump fastlane from 2.234.0 to 2.235.0 (#1761) via dependabot[bot] (@dependabot[bot]) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Release bundles hybrid-common 18.8.0 and documents new ad-tracking APIs; diff is mostly version pins but consumers get new attribution surface and native SDK changes. > > **Overview** > This **automatic release** cuts **10.2.0** for `purchases_flutter` and `purchases_ui_flutter` by bumping version strings everywhere they are reported (`.version`, `pubspec.yaml`, Android/iOS/macOS podspecs and Gradle, and platform flavor version constants on Android, iOS, and web). > > It aligns native dependencies on **`purchases-hybrid-common` 18.8.0** (Android `common_version`, iOS `PurchasesHybridCommon`, web hybrid mappings CDN) and updates **`purchases_ui_flutter`** to depend on **`purchases_flutter` ^10.2.0**. **CHANGELOG** / **CHANGELOG-LATEST** document the release: **ad tracking API** (#1750), hybrid-common bumps (18.8.0 / 18.7.1) with linked Android/iOS/Web SDK versions, and a **fastlane** tooling bump. > > There is **no application logic change in this diff** beyond versioning and release notes—the feature work ships via the aggregated changelog and underlying hybrid-common release. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6c60efe. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Summary
Adds five new methods to track ad lifecycle events through RevenueCat, exposed via
Purchases.adTracker:Purchases.adTracker.trackAdDisplayed(AdDisplayedData)Purchases.adTracker.trackAdOpened(AdOpenedData)Purchases.adTracker.trackAdLoaded(AdLoadedData)Purchases.adTracker.trackAdRevenue(AdRevenueData)Purchases.adTracker.trackAdFailedToLoad(AdFailedToLoadData)All methods and types are annotated
@experimental.Key decisions
PurchasesAdTrackersingleton — methods are grouped onPurchases.adTracker(private constructor, defined in the same file asPurchasesto share_channel) rather than as flat static methods onPurchases. Matches the pattern used in other SDKs.Flat separate data classes —
AdDisplayedData,AdOpenedData,AdLoadedData,AdRevenueData,AdFailedToLoadDataare independent types rather than a shared base class. Mirror the iOS and Android SDK design: identical fields today, kept separate for semantic clarity.Typed wrappers —
AdMediatorName,AdFormat,AdRevenuePrecisionare open-ended value types with predefined constants, not enums. Matches the native SDK pattern since ad networks and formats aren't a closed set.trackAdFailedToLoadhas noimpressionIdornetworkName— consistent with PHC: a failed load produces neither an impression nor a serving network;mediatorErrorCodeis added instead.iOS availability guard —
@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)matches PHC's own guard; logs a warning on older OS versions.Testing
api_tester(compile-time shape checks)Note
Medium Risk
New experimental public API and ad revenue event forwarding to native SDKs; low direct purchase/auth impact but incorrect payloads could skew analytics.
Overview
Adds an experimental ad lifecycle analytics surface on
Purchases.adTracker, with five events: displayed, opened, loaded, revenue, and failed-to-load. Each call takes a dedicated payload type (AdDisplayedData,AdRevenueData, etc.) serialized viatoMap()over the existing method channel to Purchases Hybrid Common on Android and iOS (iOS 15+; older OS versions log and no-op). Web treats these channel methods as no-ops.New open-ended value types (
AdMediatorName,AdFormat,AdRevenuePrecision) mirror native SDKs;AdFailedToLoadDataomits impression/network fields and supports optionalmediatorErrorCode. API shape is locked by api_tester compile checks and unit tests for channel arguments;metais added for@experimental.Reviewed by Cursor Bugbot for commit 515a8a7. Bugbot is set up for automated code reviews on this repo. Configure here.