Expose trackCustomPaywallImpression as experimental public API#6427
Conversation
b7449f6 to
caf32cb
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing
Sendableconformance onCustomPaywallImpressionParams- Added explicit
Sendableconformance toCustomPaywallImpressionParamsso it can be safely captured in theTaskused bytrackCustomPaywallImpression.
- Added explicit
Or push these changes by commenting:
@cursor push fdeb3da1f3
Preview (fdeb3da1f3)
diff --git a/Sources/Paywalls/Events/CustomPaywallEvent.swift b/Sources/Paywalls/Events/CustomPaywallEvent.swift
--- a/Sources/Paywalls/Events/CustomPaywallEvent.swift
+++ b/Sources/Paywalls/Events/CustomPaywallEvent.swift
@@ -78,7 +78,7 @@
/// Parameters for tracking a custom paywall impression event.
@_spi(Experimental) @objc(RCCustomPaywallImpressionParams)
-public final class CustomPaywallImpressionParams: NSObject {
+public final class CustomPaywallImpressionParams: NSObject, Sendable {
/// An optional identifier for the custom paywall being shown.
@objc public let paywallId: String?
ajpallares
left a comment
There was a problem hiding this comment.
Looks good! I have some initial comments
caf32cb to
65b09d4
Compare
daa0fb6 to
6c6c10a
Compare
ajpallares
left a comment
There was a problem hiding this comment.
Amazing job! I have some comments, the most important one being a file commited that I believe is not related. But looks great otherwise!
| .init(paywallId: params.paywallId) | ||
| ) | ||
| await self.eventsManager?.track(featureEvent: event) | ||
| @objc public func trackCustomPaywallImpression(_ params: CustomPaywallImpressionParams) { |
There was a problem hiding this comment.
Since this is a public API that consumers will likely call from SwiftUI's onAppear (which can fire multiple times during tab switches, sheet re-presentations, etc.), each invocation creates a brand new impression event with a fresh UUID and triggers a priority flush. There's nothing preventing duplicate impressions for the same paywall display.
Should we add doc comment guidance about when to call this (e.g., "Call once per paywall presentation, not in onAppear")?
I'm not sure, but I'm afraid we could be getting duplicate impressions.
There was a problem hiding this comment.
Yep that's indeed true. I've added a comment about this. LMK what you think :)
There was a problem hiding this comment.
I like the callout. It should help a bit avoid multiple impressions. Thank you!
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
e3877c9 to
c6adc8c
Compare
ajpallares
left a comment
There was a problem hiding this comment.
Thank you again! Looks great! 🚀
| .init(paywallId: params.paywallId) | ||
| ) | ||
| await self.eventsManager?.track(featureEvent: event) | ||
| @objc public func trackCustomPaywallImpression(_ params: CustomPaywallImpressionParams) { |
There was a problem hiding this comment.
I like the callout. It should help a bit avoid multiple impressions. Thank you!
| expect(data.paywallId).to(beNil()) | ||
| } | ||
|
|
||
| func testTrackMultipleImpressionsInQuickSuccession() async throws { |
7e1746d to
5fdd54f
Compare
## Description Exposes `trackCustomPaywallImpression` as an experimental public API, allowing hybrid SDKs and consumers to track impressions of custom (non-RevenueCat) paywalls. ### Changes - New `CustomPaywallEventParams` class gated behind `@ExperimentalPreviewRevenueCatPurchasesAPI` - Public `trackCustomPaywallImpression` method on `Purchases` - API tester and Metalava API surface updates ### Related PRs - purchases-ios: RevenueCat/purchases-ios#6427 - purchases-hybrid-common: RevenueCat/purchases-hybrid-common#1537 - react-native-purchases: RevenueCat/react-native-purchases#1660 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces new public (experimental) API surface on `Purchases`, which can affect binary compatibility and downstream integrations if the signature or opt-in semantics change. > > **Overview** > Exposes `Purchases.trackCustomPaywallImpression` as an *experimental public API* (with overloads) to let apps track impressions for custom/non-RevenueCat paywalls, accepting optional `paywallId` via the new `CustomPaywallImpressionParams` type. > > Replaces the prior internal params type, updates API tester coverage (Java/Kotlin) to exercise the new calls, and refreshes Metalava API surface files and unit tests accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3896706. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Description Adds bridge methods for `trackCustomPaywallImpression` on both iOS and Android, enabling hybrid SDKs to call the new experimental native API. ### Changes - iOS: `trackCustomPaywallImpression(_ data:)` in `CommonFunctionality.swift` - Android: `trackCustomPaywallImpression(data)` in `common.kt` Both methods accept a dictionary with an optional `paywallId` key and delegate to the native SDK. ### Related PRs - purchases-ios: RevenueCat/purchases-ios#6427 - purchases-android: RevenueCat/purchases-android#3199 - react-native-purchases: RevenueCat/react-native-purchases#1660
## Summary - Add `trackCustomPaywallImpression` API to the Unity SDK, wiring through the existing hybrid-common bridge on both iOS and Android - Introduce `CustomPaywallImpressionParams` class with an optional `PaywallId` - Pass `CustomPaywallImpressionParams` through the wrapper interface, splitting into separate arguments only at the native bridge boundary - Add a testing UI in the Subtester app's Paywall screen with an optional paywall ID field ## Changes - **New file:** `CustomPaywallImpressionParams.cs` — params class with optional `PaywallId` - **Interface:** `IPurchasesWrapper` — added `TrackCustomPaywallImpression(CustomPaywallImpressionParams)` - **Public API:** `Purchases.cs` — two overloads (with params, parameterless) - **iOS bridge:** `PurchasesWrapperiOS.cs` + `PurchasesUnityHelper.m` — DllImport and ObjC bridge calling `[RCCommonFunctionality trackCustomPaywallImpression:]` - **Android bridge:** `PurchasesWrapperAndroid.cs` + `PurchasesWrapper.java` — CallPurchases and static method calling `CommonKt.trackCustomPaywallImpression()` - **No-op:** `PurchasesWrapperNoop.cs` — empty stub - **API tests:** `PurchasesAPITests.cs` — compile-time verification of both overloads - **Subtester:** `PaywallScreen.cs` — "Custom Paywall Events" section with paywall ID field and button ### Related PRs - purchases-ios: RevenueCat/purchases-ios#6427 - purchases-android: RevenueCat/purchases-android#3199 - purchases-hybrid-common: RevenueCat/purchases-hybrid-common#1537 - react-native-purchases: RevenueCat/react-native-purchases#1660 - purchases-flutter: RevenueCat/purchases-flutter#1679 - purchases-capacitor: RevenueCat/purchases-capacitor#725 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Rick van der Linden <rick.vanderlinden@revenuecat.com>
## Summary - Add `trackCustomPaywallImpression` API to React Native, enabling developers to track impressions of custom (non-RevenueCat) paywalls for analytics - Add purchase tester demo screen for testing custom paywall impressions ## Changes - **iOS bridge:** `RCT_EXPORT_METHOD(trackCustomPaywallImpression:)` in `RNPurchases.m` - **Android bridge:** `@ReactMethod trackCustomPaywallImpression` in `RNPurchasesModule.java` - **TypeScript:** `Purchases.trackCustomPaywallImpression()` in `purchases.ts` with optional `paywallId` parameter - **Purchase tester:** Added a demo Custom Paywall screen for testing impressions ### Related PRs - purchases-ios: RevenueCat/purchases-ios#6427 - purchases-android: RevenueCat/purchases-android#3199 - purchases-hybrid-common: RevenueCat/purchases-hybrid-common#1537 - purchases-flutter: RevenueCat/purchases-flutter#1679 - purchases-capacitor: RevenueCat/purchases-capacitor#725
…PI (#1565) ## Summary - Read `offeringId` from the data dictionary in `trackCustomPaywallImpression` on both iOS and Android bridge layers - Pass it through to the native `CustomPaywallImpressionParams`, allowing hybrid SDKs to override the default offering identifier ## Related PRs - purchases-ios: RevenueCat/purchases-ios#6427 - purchases-android: RevenueCat/purchases-android#3199 - react-native-purchases: RevenueCat/react-native-purchases#1660 - purchases-unity: RevenueCat/purchases-unity#863

Description
Exposes
trackCustomPaywallImpressionas an experimental public API, allowing hybrid SDKs and consumers to track impressions of custom (non-RevenueCat) paywalls.Changes
CustomPaywallImpressionParamsclass gated behind@_spi(Experimental),@objc-compatibletrackCustomPaywallImpressionmethod onPurchases(fire-and-forget, wraps inTask {})Related PRs
Note
Medium Risk
Introduces a new experimental public/ObjC API on
Purchases, which changes the SDK’s public surface and event-tracking call pattern (fire-and-forgetTask) and could affect analytics behavior if misused.Overview
Exposes experimental custom paywall impression tracking to SDK consumers by adding
CustomPaywallImpressionParams(ObjC-compatible) and makingPurchases.trackCustomPaywallImpressiona public@objcfire-and-forget API, plus a no-params convenience overload.Removes the previously internal
CustomPaywallEvent.Paramstype, updates unit tests accordingly, and adds Swift/ObjC API tester coverage to validate the new API surface. Project configuration is updated to include the new source/test files.Written by Cursor Bugbot for commit 5fdd54f. This will update automatically on new commits. Configure here.