Skip to content

feat(admob): add next-gen app open ad tracking - #4013

Merged
polmiro merged 7 commits into
next-gen-admob-sdkfrom
app-open-ad-support
Aug 26, 2026
Merged

feat(admob): add next-gen app open ad tracking#4013
polmiro merged 7 commits into
next-gen-admob-sdkfrom
app-open-ad-support

Conversation

@polmiro

@polmiro polmiro commented Aug 19, 2026

Copy link
Copy Markdown
Member

Motivation

Expose Google Mobile Ads Next-Gen app-open loading and event tracking through a public RevenueCat API.

Description

  • Add callback-based and suspending AdTracker.loadAndTrackAppOpenAd APIs for request-based loading through Google's Next-Gen AdRequest API.
  • Add callback-based loadAndTrackAppOpenAdFromResponse for server-to-server ad responses, with an explicit ad unit ID for attribution.
  • Track successful and failed loads while preserving Google's original callbacks and suspending result.
  • Install lifecycle and paid-event tracking before a loaded ad reaches application code.
  • Add AppOpenAd.show(activity, placement) for overriding the tracking placement when the display location is only known at show time.
  • Add setTrackingAdEventCallback so applications can replace their callback without removing RevenueCat's wrapper.
  • Publish and document the experimental Kotlin API.

Tracking behavior

The load-time placement is used for load events and as the default for subsequent lifecycle events. A placement passed to show takes precedence for display, click, and revenue events.

Full-screen display is tracked from onAdShowedFullScreenContent; impressions are not also counted as displays. RevenueCat tracking runs before application callbacks.

Assigning adEventCallback directly after a tracked load replaces RevenueCat's wrapper. Applications should provide the callback while loading or use setTrackingAdEventCallback.

If show(activity, placement) is called after the callback was manually replaced, the ad is still shown but the placement override is ignored and a warning is logged.

Validation

  • ./gradlew :feature:admob-next-gen:testDebugUnitTest
  • ./gradlew detektAll :feature:admob-next-gen:lintDefaultsDebug :feature:admob-next-gen:metalavaCheckCompatibilityDefaultsDebug
  • ./gradlew :feature:admob-next-gen:metalavaGenerateSignatureDefaultsDebug

Note

Medium Risk
New experimental public APIs affect ad revenue analytics attribution; incorrect callback assignment can silently drop tracking, but behavior matches existing interstitial adapters.

Overview
Adds RevenueCat ad tracking for Google Mobile Ads Next-Gen app open ads on the direct load path (alongside existing preload support), mirroring the interstitial/rewarded pattern.

New AdTracker entry points: callback and suspending loadAndTrackAppOpenAd, plus loadAndTrackAppOpenAdFromResponse for server-to-server responses with an explicit adUnitId. Successful loads install a tracking event callback before the app’s load callback runs; Google’s callbacks and suspend AdLoadResult are unchanged.

AppOpenAd.show(activity, placement) overrides analytics placement at show time (including clearing placement when null), and setTrackingAdEventCallback lets apps swap lifecycle handlers without dropping the RevenueCat wrapper. Public api.txt, README usage/placement guidance, and unit tests cover load, suspend, response, placement override, and callback wiring.

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

@polmiro polmiro added pr:feat A new feature pr:other and removed pr:feat A new feature labels Aug 19, 2026
@polmiro polmiro changed the title feat(admob): add next-gen app open tracking feat(admob): add next-gen app open ad tracking Aug 19, 2026
@polmiro
polmiro force-pushed the add-next-gen-admob-event-tracking branch from b1c6581 to 138ff80 Compare August 19, 2026 11:33
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.01%. Comparing base (c37bef0) to head (de9355e).
⚠️ Report is 1 commits behind head on next-gen-admob-sdk.

Additional details and impacted files
@@                  Coverage Diff                   @@
##           next-gen-admob-sdk    #4013      +/-   ##
======================================================
+ Coverage               82.99%   83.01%   +0.01%     
======================================================
  Files                     437      437              
  Lines                   17893    17893              
  Branches                 2684     2684              
======================================================
+ Hits                    14851    14854       +3     
+ Misses                   2148     2146       -2     
+ Partials                  894      893       -1     

☔ View full report in Codecov by Harness.
📢 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.

Base automatically changed from add-next-gen-admob-event-tracking to next-gen-admob-sdk August 19, 2026 11:51
@polmiro
polmiro force-pushed the app-open-ad-support branch from 854bc05 to a24f239 Compare August 19, 2026 12:09
@polmiro
polmiro marked this pull request as ready for review August 19, 2026 12:25
@polmiro
polmiro requested a review from a team as a code owner August 19, 2026 12:25
@polmiro
polmiro requested review from AlvaroBrey, ajpallares, peterporfy and tonidero and removed request for ajpallares August 19, 2026 15:30
polmiro added a commit that referenced this pull request Aug 19, 2026
…ide (#4034)

### Motivation

Two related bits of drift in the next-gen module.

**Log messages were inline literals.** The rest of the SDK keeps them in
a strings object — `purchases` has fifteen under
`com.revenuecat.purchases.strings`, and `feature/galaxy` has
`GalaxyStrings`. The two AdMob adapters are the exception, and legacy
only half follows it: `RewardVerificationStrings` holds a single
constant while six other call sites inline their text.

**The placement override is copy-pasted per format.** Every full-screen
format resolves a show-time override the same way — cast
`adEventCallback` to the tracking wrapper, assign `placement`, warn when
the cast fails. There are six copies across five branches
(`InterstitialAdExtensions.kt` #3985, `RewardedAdExtensions.kt` #4011,
`RewardedInterstitialAdExtensions.kt` #4012, `AppOpenAdExtensions.kt`
#4013, and two in `RewardVerificationExtensions.kt` #4017), and the
warning text already differs between them.

Nothing pulls those copies back together. Because they live in
**separate files**, git merges every one cleanly when those branches
land here, and the divergent wording survives silently — there is no
conflict to catch it.

Worth noting the legacy adapter already routes its four formats through
a single `applyPlacementOverride`. Next-gen dropped that shape; this
restores it rather than inventing something new, and keeps the same
name.

### Description

Two commits:

1. **`AdMobNextGenStrings`** — holds the module's three log messages
(the not-configured warning, the tracking-failure message, and the new
placement-override warning). Existing `TrackIfConfigured` call sites
route through it.
2. **`AdEventCallback?.applyPlacementOverride(placement)`** — holds the
cast condition and the warning. All six call sites collapse to one line:
   ```kotlin
   adEventCallback.applyPlacementOverride(placement)
   ```

The receiver is the shared `AdEventCallback` supertype rather than each
format's callback interface — every format callback
(`RewardedAdEventCallback`, `InterstitialAdEventCallback`,
`AppOpenAdEventCallback`, `RewardedInterstitialAdEventCallback`) extends
it, and the per-format cast was never load-bearing: an ad's
`adEventCallback` can only ever hold its own format's wrapper.

The KDoc also records that passing `null` clears the load-time
placement, which was previously documented only per-format.

### Adoption

The helper is intentionally unused here — the extensions that call it
live on the format branches, not on this base. Once this merges, each
format PR swaps its inline block for the one-liner as it rebases.
Verified that an unused `internal` function passes `detektAll` on this
branch, so this lands green on its own.

`TrackIfConfigured.kt` is byte-identical across all ten
format/preload/SSV branches, so moving its strings conflicts with none
of them.

### Validation

- `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest`
- `./gradlew detektAll`
- `./scripts/api-check.sh` — no public API change; both additions are
`internal`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Internal refactor of logging and duplicated placement logic with no
public API or runtime behavior change beyond unified warning text.
> 
> **Overview**
> Introduces **`AdMobNextGenStrings`** with three shared log messages
(Purchases not configured, tracking failure, placement override ignored)
and routes **`trackIfConfigured`** through them instead of inline
literals.
> 
> Adds **`AdEventCallback?.applyPlacementOverride(placement)`** so
show-time placement override logic (cast to `TrackingAdEventCallback`,
assign placement, warn on failure) lives in one place, aligned with the
legacy AdMob adapter. The helper is **`internal`** and not wired from
format extension files on this branch; those branches are expected to
replace duplicated blocks with a single call after merge.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
d9a56d2. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@polmiro
polmiro force-pushed the app-open-ad-support branch from 08f9492 to e98a5c5 Compare August 20, 2026 13:38
@RevenueCat-Danger-Bot

RevenueCat-Danger-Bot commented Aug 20, 2026

Copy link
Copy Markdown
Public API changes in feature:admob-next-gen (6 new declarations)
+ method @KotlinOnly @com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackAppOpenAd(com.revenuecat.purchases.ads.even…
+ method @KotlinOnly @com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI @kotlin.jvm.JvmSynthetic public static void loadAndTrackAppOpenAd(com.revenuecat.purchases.ads.events.AdTracke…
+ method @KotlinOnly @com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI @kotlin.jvm.JvmSynthetic public static void loadAndTrackAppOpenAdFromResponse(com.revenuecat.purchases.ads.eve…
+ public final class AppOpenAdExtensionsKt
+ method @KotlinOnly @com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile…
+ method @KotlinOnly @com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd…

Generated by 🚫 Danger

@polmiro
polmiro force-pushed the app-open-ad-support branch 2 times, most recently from ff33e1c to 50c1010 Compare August 26, 2026 08:48
The warning named the least likely cause. It fires whenever the ad was not
loaded through loadAndTrack, which is usually because the ad was loaded with
Google's own API rather than because someone reassigned adEventCallback, so it
sent readers looking for a reassignment that never happened.

The KDoc also only promised that the show-time placement takes precedence. It
did not say that passing null clears the placement recorded at load time, which
is easy to hit because the parameter has no default and the SDK's own show is
the only way to keep the load-time value.
The base branch now holds applyPlacementOverride, so drop this format's copy of
the cast, the assignment and the warning in favour of the shared one. That leaves
a single definition of the message rather than one per format.
@polmiro
polmiro force-pushed the app-open-ad-support branch from 50c1010 to de9355e Compare August 26, 2026 09:08
@polmiro
polmiro merged commit 674d3a4 into next-gen-admob-sdk Aug 26, 2026
34 checks passed
@polmiro
polmiro deleted the app-open-ad-support branch August 26, 2026 09:25
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.

3 participants