Skip to content

Add non paid revenue reporting infra#2728

Merged
tonidero merged 20 commits into
mainfrom
rc-ads-tracking
Nov 20, 2025
Merged

Add non paid revenue reporting infra#2728
tonidero merged 20 commits into
mainfrom
rc-ads-tracking

Conversation

@tonidero

@tonidero tonidero commented Oct 14, 2025

Copy link
Copy Markdown
Contributor

Description

Implements ad event tracking infrastructure for RevenueCat. This provides the client-side foundation needed to report ad impressions, clicks, and revenue alongside subscription data for comprehensive LTV tracking.

In follow-up PRs:

private val appSessionID: UUID = Companion.appSessionID,
private val legacyEventsFileHelper: EventsFileHelper<PaywallStoredEvent>,
private val fileHelper: EventsFileHelper<BackendStoredEvent>,
private val adFileHelper: EventsFileHelper<BackendStoredEvent.Ad>,

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.

This PR is still WIP but this is the last approach I mentioned here. I believe it minimizes the code duplication quite a bit. Wdyt @polmiro @JayShortway ?

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.

Lmk what you think about this approach (different track methods for different types of events). I think we could also pass the URL as a parameter to the events manager and create multiple event managers. However, I don't think the current approach is that error-prone since the types of events for ads and current are different.

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 agree with having different storage files given they function as a queue that gets cleared independently.

When it comes to event managers I am a bit in the middle, let me share my thoughts see what you think:

  • Tracking:
    • From a caller perspective all the event tracking calls should be going through the same object. And how that gets delivered is an implementation detail. This hints to me that we should have a single events manager when it comes to tracking.
  • Flushing:
    • Paywall events flushing: There are a couple of trigger points that specific only to the paywall events. These in my opinion should only go to the event manager that handles paywall events and not the one handling ad events.
    • Ad events flushing: Given the amount of events, there may be added complexities to handle in the ad event manager (just throwing some rough potential ideas):
      • sending more one batch of events at a time.
      • having some sort of time-based flushing to avoid waiting too long
      • having some event count flushing to prevent accumulating too many events.
    • Maybe these apply to both event managers, maybe not, I am not sure.
    • These hint to me that we may want separate events manager when it comes to flushing.

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.

Hmm well, regarding flushing behavior, if we want to have different behavior between ad and paywalls&customerCenter events, having it be 2 instances of the EventManger might be a bit trickier... Right now the EventManger, syncs only the oldest 50 events for paywalls/customer center events. But maybe we want to perform multiple requests for Ads, if there are more than 50 events, so they don't accumulate, which would require changes inside the EventManager...

Having said that, I think it would be better to try to keep the logic for flushing the same/similar, since the events seem relatively similar in importance. At this point, I think I could go either way. Will implement the 2 instances of the EventManager though, since it might be slightly cleaner 👍

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 agree with the thoughts, this is why I wanted to separate this part in a different PR since it seems prone to require some more back and forth.

@tonidero tonidero left a comment

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.

Not finished, still need to verify property names, add tests etc, but would appreciate a look @polmiro @RevenueCat/coresdk

private val appSessionID: UUID = Companion.appSessionID,
private val legacyEventsFileHelper: EventsFileHelper<PaywallStoredEvent>,
private val fileHelper: EventsFileHelper<BackendStoredEvent>,
private val adFileHelper: EventsFileHelper<BackendStoredEvent.Ad>,

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.

Lmk what you think about this approach (different track methods for different types of events). I think we could also pass the URL as a parameter to the events manager and create multiple event managers. However, I don't think the current approach is that error-prone since the types of events for ads and current are different.

@get:Synchronized
@set:Synchronized
private var flushInProgress = false
private var flushInProgress = AtomicBoolean(false)

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.

This felt safer 😅

@tonidero tonidero requested review from a team and polmiro October 15, 2025 16:27
@codecov

codecov Bot commented Oct 16, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.09901% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.15%. Comparing base (1831083) to head (3d41fbb).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...n/com/revenuecat/purchases/ads/events/AdTracker.kt 75.00% 6 Missing and 5 partials ⚠️
...purchases/ads/events/networking/AdEventsRequest.kt 0.00% 3 Missing ⚠️
...evenuecat/purchases/common/events/EventsManager.kt 87.50% 0 Missing and 3 partials ⚠️
...otlin/com/revenuecat/purchases/PurchasesFactory.kt 94.73% 1 Missing ⚠️
...revenuecat/purchases/common/events/BackendEvent.kt 95.23% 0 Missing and 1 partial ⚠️
...ecat/purchases/common/events/BackendStoredEvent.kt 97.77% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2728      +/-   ##
==========================================
+ Coverage   77.98%   78.15%   +0.17%     
==========================================
  Files         320      323       +3     
  Lines       12520    12706     +186     
  Branches     1727     1743      +16     
==========================================
+ Hits         9764     9931     +167     
- Misses       2028     2038      +10     
- Partials      728      737       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tonidero tonidero changed the title WIP: Add non paid revenue reporting infra Add non paid revenue reporting infra Nov 14, 2025
@tonidero tonidero marked this pull request as ready for review November 14, 2025 12:12
/**
* Tracks ad-related events such as ad displays, opens, and revenue.
*/
@InternalRevenueCatAPI

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.

Note that I'm marking this as @InternalRevenueCatAPI, so developers would need to explicitedly opt-in to use it, but it can still theoretically be used in the wild (very unlikely), unlike in iOS, where it's behind a compiler flag. I can add a compiler flag to disable tracking at all until we're ready, but I was wondering what the plan is to ship this? @polmiro

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.

What there be the risks of someone using this? From the stability of public APIs perspective, I think this is OK, even if we want to make changes to it while marked as @InternalRevenueCatAPI

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.

It's behind a feature flag in the backend as well, so it would basically have events that would fail to send, and since it's with a 4xx, it would mark them as sent, so I think very low right now.

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt Outdated
@tonidero tonidero requested a review from JayShortway November 18, 2025 14:05

@ajpallares ajpallares 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.

Great work on this!

/**
* Tracks ad-related events such as ad displays, opens, and revenue.
*/
@InternalRevenueCatAPI

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.

What there be the risks of someone using this? From the stability of public APIs perspective, I think this is OK, even if we want to make changes to it while marked as @InternalRevenueCatAPI

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated
@tonidero tonidero enabled auto-merge November 20, 2025 12:24

@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!

@tonidero tonidero added this pull request to the merge queue Nov 20, 2025
Merged via the queue into main with commit 679a2cb Nov 20, 2025
23 checks passed
@tonidero tonidero deleted the rc-ads-tracking branch November 20, 2025 12:52
github-merge-queue Bot pushed a commit that referenced this pull request Nov 25, 2025
**This is an automatic release.**

> [!WARNING]  
> If you don't have any login system in your app, please make sure your
one-time purchase products have been correctly configured in the
RevenueCat dashboard as either consumable or non-consumable. If they're
incorrectly configured as consumables, RevenueCat will consume these
purchases. This means that users won't be able to restore them from
version 9.0.0 onward.
> Non-consumables are products that are meant to be bought only once,
for example, lifetime subscriptions.


## RevenueCat SDK
### 🐞 Bugfixes
* Restore Purchases config automatically in CustomerCenter (#2867) via
Facundo Menzella (@facumenzella)
* Handle error reading `errorStream` in some devices (#2865) via Toni
Rico (@tonidero)
* [MON-1122] Revert variable rounding logic to not round up (#2857) via
Pol Piella Abadia (@polpielladev)

## RevenueCatUI SDK
### Paywallv2
#### 🐞 Bugfixes
* Select default package on Sheet dismissal (#2861) via Cesar de la Vega
(@vegaro)
### Customer Center
#### ✨ New Features
* CC-581 | Allow for support ticket creation (#2810) via Rosie Watson
(@RosieWatson)

### 🔄 Other Changes
* Bump fastlane-plugin-revenuecat_internal from `7328ea7` to `efca663`
(#2864) via dependabot[bot] (@dependabot[bot])
* Bump fastlane from 2.228.0 to 2.229.0 (#2863) via dependabot[bot]
(@dependabot[bot])
* Bump fastlane-plugin-revenuecat_internal from `083ced9` to `7328ea7`
(#2862) via dependabot[bot] (@dependabot[bot])
* Runs plugin actions from correct directory (#2858) via JayShortway
(@JayShortway)
* Flush multiple event batches (#2842) via Toni Rico (@tonidero)
* Add file size limit to events tracking files (#2841) via Toni Rico
(@tonidero)
* Make events manager be supported in Android < 24 (#2854) via Toni Rico
(@tonidero)
* Add non paid revenue reporting infra (#2728) via Toni Rico (@tonidero)
* Fix backend integration tests (#2860) via Toni Rico (@tonidero)
* Track `connection_error_reason` property in diagnostics (#2855) via
Toni Rico (@tonidero)
* Uses some git+GitHub lanes from Fastlane plugin (#2856) via
JayShortway (@JayShortway)
* Add client side timeout logic for endpoints that support fallback URLs
(#2807) via Toni Rico (@tonidero)
* [EXTERNAL] Fix deprecation warnings in examples module (#2852)
contributed by @gojoel (#2853) via Toni Rico (@tonidero)
* Bump fastlane-plugin-revenuecat_internal from `9f78bb9` to `083ced9`
(#2848) via dependabot[bot] (@dependabot[bot])

Co-authored-by: revenuecat-ops <ops@revenuecat.com>
github-merge-queue Bot pushed a commit that referenced this pull request Nov 26, 2025
…2871)

### Description
This is a follow-up from #2728 

In iOS, we're passing an object to the track methods for non paid
revenue tracking, but we were passing the individual parameters in
Android. After thinking about it, passing an object allows for better
extensibility, so modifying the API for android.

This is technically a breaking change, but the API is still internal, so
it should be ok.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants