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!
| } | ||
|
|
||
| void _checkTrackAdDisplayed() { | ||
| Future<void> future = Purchases.trackAdDisplayed(const AdDisplayedData( |
There was a problem hiding this comment.
I know this is in line with what we're doing in other APIs, but to avoid bloating the Purchases API, I think it would be ideal to keep the ad methods within a separate object, like we're doing in the natives, even if they remain static methods, so these can be for example something like:
Purchases.AdTracker.trackAdDisplayed(in a namespace if possible) orPurchasesAdTracker(if not possible to namespace).
I understand this is a bit trickier... But I think it could help to avoid some bloating issues. I also think it's fine even if internally we need to reuse the same channel to communicate with the kotlin/swift layer. Lmk what you think! 🙏
There was a problem hiding this comment.
I agree that it would be more in line with existing SDKs. Although Purchases.AdTracker won't work I think, but we can do a singleton Purchases.adTracker on the static Purchases - or a separate static PurchasesAdTracker as you suggested. I am leaning towards the second. Wdyt?
There was a problem hiding this comment.
IMO, we should avoid having static APIs... I think we have this most for legacy reasons... So I kinda would lean towards the singleton approach if possible. Feels to me that it's easier to test and iterate on 🙏
There was a problem hiding this comment.
Updated to singleton - indeed looks better
| import 'ad_format.dart'; | ||
| import 'ad_mediator_name.dart'; | ||
|
|
||
| class AdDisplayedData { |
There was a problem hiding this comment.
Just to check, these are not experimental (we don't currently have a good way to mark these as experimental in dart). Just to confirm, but are we ok with launching these as stable? And if so, should we make the native APIs stable already?
There was a problem hiding this comment.
That's a good question, I will check with the team.
There was a problem hiding this comment.
What if we don't document these features in flutter/unity/reactnative etc until it is not marked as stable in the core sdks? Would that be good enough to signal they are experimental/not official yet? Or we must have it stable in ios/android before we add anything here? Wdyt?
There was a problem hiding this comment.
Even if we don't publicize them, if we release these APIs in a release as stable, I think we should consider them stable and basically, we should try very very hard to avoid breaking changes, or they would need to be a major.
So I think the proper way is to use meta's library and add an experimental annotation to these APIs: https://api.flutter.dev/flutter/meta/experimental-constant.html. We haven't used this before but I think it's probably worth using it here...
There was a problem hiding this comment.
Oh that's nice - I've added the annotation, thanks!
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 e47ff53. Configure here.

Summary
Adds five new methods to track ad lifecycle events through RevenueCat:
Purchases.trackAdDisplayed(AdDisplayedData)Purchases.trackAdOpened(AdOpenedData)Purchases.trackAdLoaded(AdLoadedData)Purchases.trackAdRevenue(AdRevenueData)Purchases.trackAdFailedToLoad(AdFailedToLoadData)All five methods are flat static methods on
Purchases- following how it looks like on PHC.Key decisions
Flat separate data classes —
AdDisplayedData,AdOpenedData,AdLoadedData,AdRevenueData,AdFailedToLoadDataare independent types rather than a shared base class. They mirror the iOS and Android SDK design: identical fields today, but kept separate for semantic value.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 noimpressionId— consistent with PHC: a failed load doesn't produce an impression, so the field is omitted andmediatorErrorCodeis added instead.Testing
api_tester(compile-time shape checks)Note
Medium Risk
Adds new public API surface and native method-channel wiring on iOS/Android, so mismatched argument shapes or platform availability could cause runtime issues despite added tests; no changes to purchase flows or sensitive auth/data handling.
Overview
Adds an experimental ad tracking API exposed as
Purchases.adTrackerwith five methods to report ad lifecycle events (trackAdDisplayed,trackAdOpened,trackAdLoaded,trackAdRevenue,trackAdFailedToLoad) using new typed payload models (e.g.AdRevenueData,AdFormat,AdMediatorName).Wires these calls through the method channel on Android (
PurchasesFlutterPlugin.java) and iOS (PurchasesFlutterPlugin.m, gated to iOS 15+/equivalents with warning logs), and makes them no-ops on web.Updates exports/dependencies (
meta) and adds coverage viaapi_testercompile-time API checks plus unit tests asserting the method names/argument maps sent over the channel.Reviewed by Cursor Bugbot for commit cf7b774. Bugbot is set up for automated code reviews on this repo. Configure here.