Skip to content

feat: ad tracking API#1750

Merged
peterporfy merged 13 commits into
mainfrom
ads-220
May 27, 2026
Merged

feat: ad tracking API#1750
peterporfy merged 13 commits into
mainfrom
ads-220

Conversation

@peterporfy

@peterporfy peterporfy commented May 11, 2026

Copy link
Copy Markdown
Contributor

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

PurchasesAdTracker singleton — methods are grouped on Purchases.adTracker (private constructor, defined in the same file as Purchases to share _channel) rather than as flat static methods on Purchases. Matches the pattern used in other SDKs.

Flat separate data classesAdDisplayedData, AdOpenedData, AdLoadedData, AdRevenueData, AdFailedToLoadData are independent types rather than a shared base class. Mirror the iOS and Android SDK design: identical fields today, kept separate for semantic clarity.

Typed wrappersAdMediatorName, AdFormat, AdRevenuePrecision are open-ended value types with predefined constants, not enums. Matches the native SDK pattern since ad networks and formats aren't a closed set.

trackAdFailedToLoad has no impressionId or networkName — consistent with PHC: a failed load produces neither an impression nor a serving network; mediatorErrorCode is 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 surface covered by api_tester (compile-time shape checks)
  • Method channel wiring covered by unit tests
  • Manually tested with a companion Flutter sample app (google_mobile_ads, all 5 ad formats)

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 via toMap() 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; AdFailedToLoadData omits impression/network fields and supports optional mediatorErrorCode. API shape is locked by api_tester compile checks and unit tests for channel arguments; meta is added for @experimental.

Reviewed by Cursor Bugbot for commit 515a8a7. Bugbot is set up for automated code reviews on this repo. Configure here.

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.
@peterporfy peterporfy requested a review from a team as a code owner May 11, 2026 12:34
@peterporfy peterporfy marked this pull request as draft May 11, 2026 12:36
@peterporfy peterporfy marked this pull request as ready for review May 11, 2026 12:48
@peterporfy peterporfy requested a review from polmiro May 11, 2026 12:53

@tonidero tonidero left a comment

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.

Looking great! Nothing too blocking, just some thoughts. Let me know what you think!

Comment thread api_tester/lib/api_tests/purchases_flutter_api_test.dart Outdated
Comment thread lib/models/ad_displayed_data.dart
Comment thread lib/models/ad_displayed_data.dart Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread lib/models/ad_failed_to_load_data.dart
Comment on lines +147 to +154
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();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see we're not including String? networkName = data.networkName for this one. Is it intentional? AdDisplayedData, AdOpenedData, and AdLoadedData all have it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 rickvdl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Comment thread api_tester/lib/api_tests/purchases_flutter_api_test.dart
Comment thread api_tester/lib/api_tests/purchases_flutter_api_test.dart
@peterporfy peterporfy added pr:feat A new feature and removed pr:other labels May 26, 2026

@rickvdl rickvdl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work!

@peterporfy peterporfy merged commit 2af7f1f into main May 27, 2026
17 checks passed
@peterporfy peterporfy deleted the ads-220 branch May 27, 2026 07:54
RCGitBot added a commit that referenced this pull request May 27, 2026
**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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants