feat(admob): add next-gen SDK support - #3958
Conversation
da804a9 to
826c49f
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3958 +/- ##
=======================================
Coverage 83.55% 83.55%
=======================================
Files 454 454
Lines 18225 18225
Branches 2750 2750
=======================================
Hits 15228 15228
Misses 2107 2107
Partials 890 890 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
f72c7ab to
a541d70
Compare
### Checklist - [x] Unit tests are not applicable because this PR contains no functional code - [x] Follow-up issues for `purchases-ios` and hybrids are not applicable to this Android-specific module ### Motivation Prepare the Android SDK repository for Google Mobile Ads Next-Gen support by introducing the publishable module that subsequent changes will build on. ### Description - Add the public `feature/admob-next-gen` Android library with minimum SDK 24, Metalava configuration, and Maven metadata. - Pin Google Mobile Ads Next-Gen SDK 1.3.0 in the version catalog. - Allow the SDK's Cronet dependencies to resolve from Google Maven. - Register the new artifact in Gradle settings and the RevenueCat BOM. - Seed the empty API signature, consumer rules, and package structure for follow-up implementation work. - Add initial setup documentation covering requirements, dependency and BOM configuration, legacy SDK exclusions, adapter mutual exclusivity, and Next-Gen SDK initialization. No functional SDK code is included in this PR. Format-specific usage and migration guidance will land alongside the relevant follow-up implementation PRs. ### Validation - `assembleDefaultsRelease` - Metalava compatibility check for the release variant - Module lint - BOM build and Maven POM generation - Dependency graph inspection - Custom-entitlement release configuration check - README link and whitespace checks <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Scaffolding-only: Gradle, BOM, and documentation with no runtime or public API behavior changes. > > **Overview** > Introduces **`feature/admob-next-gen`** as a public Android library (`purchases-admob-next-gen`, **minSdk 24**) wired like the legacy AdMob feature: Metalava **`api.txt`**, Maven POM metadata, and dependencies on **`:purchases`** plus **`ads-mobile-sdk` 1.3.0** from the version catalog. > > **Build integration:** registers the module in **`settings.gradle.kts`**, adds a BOM constraint alongside **`feature:admob`**, and extends Google Maven content filtering with **`org.chromium.net`** so Next-Gen/Cronet artifacts resolve. > > **Docs:** README covers mutual exclusivity with **`purchases-admob`**, install/BOM snippets, legacy artifact excludes, and Next-Gen **`MobileAds.initialize`** guidance. The API surface is intentionally empty (placeholder signature only)—no functional adapter code in this PR. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 66e1661. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
f32d4ae to
809d236
Compare
### Motivation Create the shared internal runtime needed by both load tracking and post-load event tracking before those implementations split into sibling pull requests. ### Description - Add a failure-isolated guard that skips tracking until `Purchases` is configured. - Centralize adapter capture stamping for loaded, failed, displayed, opened, and revenue events. - Add shared callback contract assertions for the load and event tracking layers. - Add focused tests for configuration handling, failure isolation, and every adapter capture overload. - Remove the obsolete source placeholder now that the module contains implementation code. This PR intentionally contains no load or event callback wrappers, format-specific behavior, public API, or usage documentation. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew :feature:admob-next-gen:lintDefaultsDebug detektAll` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Internal-only foundation with no public API or live ad callbacks; changes are isolated to the new module and well covered by unit tests. > > **Overview** > Adds **internal** building blocks for AdMob next-gen ad event tracking ahead of load and post-load callback wrappers in follow-up PRs. > > **`trackIfConfigured`** gates all tracking on `Purchases.isConfigured`, warns when skipped, and wraps work in `runCatching` so RevenueCat tracking failures never propagate out of Google Mobile Ads callbacks. > > **`AdTracker.trackFromAdapter`** overloads route loaded, failed-to-load, displayed, opened, and revenue events through the public tracker with **`AdCaptureMethod.ADAPTER`** so adapter-captured events differ from manual developer calls. > > A small **`Logger`** (`PurchasesAdMob` tag) backs those paths. **`assertOverridesAllSdkCallbacks`** (reflection) will enforce that future tracking callbacks cover every public SDK callback method. > > Gradle enables unit test default return values and **`libs.bundles.test`**; tests cover configuration gating, failure isolation, adapter capture stamping, and the callback contract helper. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5ef64dc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Add the load-lifecycle layer for RevenueCat ad tracking in the Google Mobile Ads Next-Gen integration. This PR builds on the shared tracking runtime in #3990 and intentionally excludes display, impression, click, revenue, and public format loading APIs. ### Description - Wrap generic Google Mobile Ads load callbacks and report successful and failed loads through RevenueCat’s adapter capture path. - Wrap banner refresh callbacks and read response information when the refresh fires so refreshed creatives use their current network and response ID. - Wrap the native loader’s distinct callback contract, tracking standard and custom-native fills as native, banner fills as banner, and forwarding completion callbacks unchanged. - Report a single native failure event for native-loader failures because the SDK reports one failed request, not a failure for every candidate format. - Provide configuration hooks that run after successful load tracking and before application delegation. The native-loader hook applies only to standard `NativeAd` results; custom-native and banner fills are tracked and delegated without using that hook. - Map load metadata including network, response ID, placement, ad unit, format, and numeric SDK error values. - Preserve application behavior when RevenueCat is unconfigured or tracking fails; failed loads are never configured. - Verify that every wrapped Google callback is explicitly overridden and forwarded. ### Stack - Based on #3990. - Targets the `agent/add-next-gen-admob-tracking-foundation` branch until the foundation merges into `next-gen-admob-sdk`. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew :feature:admob-next-gen:lintDefaultsDebug detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Internal adapter instrumentation with defensive error handling; no changes to core Purchases or public ad APIs beyond documented next-gen module behavior. > > **Overview** > Adds the **load-lifecycle** layer for the AdMob Next-Gen adapter: callback wrappers that emit RevenueCat **Ad Loaded** and **Ad Failed to Load** events (with `AdCaptureMethod.ADAPTER`) before forwarding every Google Mobile Ads callback to the app. > > **Generic loads** use `TrackingAdLoadCallback`, which tracks success/failure, runs an optional `configureAd` hook only on successful loads (track → configure → delegate), and maps network, response ID, placement, ad unit, format, and numeric SDK error codes. > > **Banner auto-refresh** uses `TrackingBannerAdRefreshCallback`, treating refresh success/failure as load events and reading `ResponseInfo` when the refresh callback fires so refreshed creatives get the current network and impression id. > > **Native loader** uses `TrackingNativeAdLoaderCallback` for the multi-callback contract: standard/custom native tracked as native, banner fills as banner; `configureAd` applies only to standard `NativeAd`; `onAdLoadingCompleted` is forwarded without tracking. > > Shared helpers include `trackIfConfigured` (skip or swallow errors so app callbacks always run), `AdapterTracking` extensions, and internal `Logger`. README documents tracked events and forwarding behavior. Unit tests cover ordering, metadata, unconfigured Purchases, and reflection-based SDK callback contract coverage. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7951730. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Complete the ad event lifecycle for RevenueCat tracking in the Google Mobile Ads Next-Gen integration. This PR is stacked on the load-tracking PR and intentionally focuses on events emitted after an ad has loaded. Nothing wires these callbacks into a public entry point yet, so everything added here is `internal` and reachable only from tests; wiring lands in the follow-up PR in this stack. ### Description - Track banner and native displays from impression callbacks. - Track fullscreen displays from the fullscreen-show callback. - Track clicks as opened events. - Track paid callbacks as revenue events with network, response, placement, currency, and micros data. - Map every Google Mobile Ads precision value to the corresponding RevenueCat precision, falling back to `UNKNOWN` for constants introduced by an SDK newer than the one this module is compiled against, so a precompiled artifact cannot throw inside the paid callback. - Read response info at event time rather than capturing it at load, so auto-refreshing banners report the currently displayed creative instead of the first-loaded one. - Preserve all common and format-specific application callbacks by delegating after tracking. - Verify in tests that every callback each SDK interface exposes is overridden and forwarded to the application delegate unchanged, and that swapping the delegate or the placement is reflected at event time. - Declare per format the event each SDK callback must produce, and assert that declaration matches the SDK's callback surface exactly, so a stray tracking call cannot ship and a callback added by a newer SDK cannot slip through untracked. Display tracking uses one explicit callback per format and does not keep deduplication state. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew :feature:admob-next-gen:lintDefaultsDebug` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes ad analytics and revenue attribution semantics (display deduplication per format, event-time response info) across all formats; internal-only until wired, but incorrect mapping would skew metrics. > > **Overview** > Adds **internal** post-load RevenueCat ad tracking for the Google Mobile Ads Next-Gen adapter (display, opened, revenue), still not wired to public APIs in this PR. > > **`TrackingAdEventCallback`** and per-format wrappers track **Ad Displayed** from `onAdImpression` (banner/native) or `onAdShowedFullScreenContent` (full-screen formats), **Ad Opened** from clicks, and **Ad Revenue** from `onAdPaid`, with mediation network and impression id read via a **`responseInfoProvider` at event time** so refreshing banners stay tied to the on-screen creative. Placement is also read at event time on event callbacks; banner refresh tracking keeps placement fixed at load. > > **`AdMobPrecisionMapping`** maps GMA `PrecisionType` to RevenueCat precision, with a non-exhaustive `else` → `UNKNOWN` so newer SDK constants on a precompiled adapter do not crash the paid callback. > > All SDK callbacks are forwarded to the app delegate after tracking (or skipped when Purchases is not configured). README documents the expanded event set, display triggers, and forwarding behavior. > > Tests add precision mapping coverage, behavioral tests for delegate/placement/response-info timing, and contract tests driven by fixtures that require every SDK callback to be overridden, forwarded, and to emit exactly the declared tracking event (or none). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 138ff80. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Public API changes in feature:admob-next-gen (68 new declarations)+ public final class AdTrackerAdMobExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackAppOpenAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.AdR…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackAppOpenAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.AdRequest adRe…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackAppOpenAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, String adResponse, String adUnitId, optional String? …
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackBannerAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.banner.AdView adView, …
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackBannerAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackBannerAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.banner.AdV…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackInterstitialAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.AdRequest…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackInterstitialAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.commo…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackInterstitialAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, String adResponse, String adUnitId, optional Str…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackNativeAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.nativead.Na…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackNativeAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdReque…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackNativeAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, String adResponse, String adUnitId, optional String? p…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackNativeAds(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.nativead.N…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackNativeAds(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdRequ…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackRewardedAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.AdRequest adR…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackRewardedAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.Ad…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackRewardedAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, String adResponse, String adUnitId, optional String?…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackRewardedInterstitialAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.sdk.common.A…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackRewardedInterstitialAd(com.revenuecat.purchases.ads.events.AdTracker, com.google.android.libraries.ads.mobile.s…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackRewardedInterstitialAdFromResponse(com.revenuecat.purchases.ads.events.AdTracker, String adResponse, String adUnitId, opti…
+ public final class AdViewExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackAd(com.google.android.libraries.ads.mobile.sdk.banner.AdView, com.google.android.libraries.ads.mobile.sdk.banner.BannerAdR…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static suspend Object? loadAndTrackAdFromResponse(com.google.android.libraries.ads.mobile.sdk.banner.AdView, String adResponse, String adUnitId, o…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void loadAndTrackAdFromResponse(com.google.android.libraries.ads.mobile.sdk.banner.AdView, String adResponse, String adUnitId, optional Str…
+ public final class AppOpenAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd, com.google.android.libraries.ads.mobile.sdk.…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd, android.app.Activity activity, String? placement);
+ public final class AppOpenAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd? pollAndTrackAd(com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAd…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.appopen.AppOpenAdPreloader.Companion, String preloadId, com.google.androi…
+ public final class BannerAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.banner.BannerAd, com.google.android.libraries.ads.mobile.sdk.ba…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingBannerAdRefreshCallback(com.google.android.libraries.ads.mobile.sdk.banner.BannerAd, com.google.android.libraries.ads.mobil…
+ public final class BannerAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.banner.BannerAd? pollAndTrackAd(com.google.android.libraries.ads.mobile.sdk.banner.BannerAdPrel…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.banner.BannerAdPreloader.Companion, String preloadId, com.google.android.…
+ public final class InterstitialAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd, com.google.android.libraries.ads.m…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd, android.app.Activity activity, String? placement);
+ public final class InterstitialAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd? pollAndTrackAd(com.google.android.libraries.ads.mobile.sdk.interst…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAdPreloader.Companion, String preloadId, com.goo…
+ public final class NativeAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.nativead.CustomNativeAd, com.google.android.libraries.ads.mobil…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.nativead.NativeAd, com.google.android.libraries.ads.mobile.sdk.…
+ public final class NativeAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdLoadResult.NativeAdLoadSuccessResult? pollAndTrackAd(com.google.android.librar…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdPreloader.Companion, String preloadId, com.google.androi…
+ public final class RewardVerificationExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void enableRewardVerification(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd);
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void enableRewardVerification(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd);
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd, android.app.Activity activity, String? placement, optional kotli…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd, android.app.Activity activity, optional kotlin.jvm.functions.Fun…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd, android.app.Activity activity, String? p…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd, android.app.Activity activity, optional …
+ public final class RewardedAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd, com.google.android.libraries.ads.mobile.sd…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd, android.app.Activity activity, String? placement, com.google.and…
+ public final class RewardedAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAd? pollAndTrackAd(com.google.android.libraries.ads.mobile.sdk.rewarded.Reward…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.rewarded.RewardedAdPreloader.Companion, String preloadId, com.google.andr…
+ public final class RewardedInterstitialAdExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void setTrackingAdEventCallback(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd, com.google.android…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static void show(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd, android.app.Activity activity, String? p…
+ public final class RewardedInterstitialAdPreloaderExtensionsKt
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAd? pollAndTrackAd(com.google.android.libraries.ads.mo…
+ method @KotlinOnly @kotlin.jvm.JvmSynthetic public static boolean startAndTrack(com.google.android.libraries.ads.mobile.sdk.rewardedinterstitial.RewardedInterstitialAdPreloader.Companion, String pr…Generated by 🚫 Danger |
### Motivation Google reports preload completion while filling a buffer, before the app adopts an ad from that buffer. The format-specific preloader extensions need one shared callback that preserves this boundary consistently. This is the foundation of the Next-Gen AdMob preloading stack and is stacked on #3957. ### Description - Add an internal preload callback that records every preload success or failure before forwarding it to the app. - Forward `onAdsExhausted` without recording a RevenueCat ad event. - Support a missing app delegate while always giving Google the internal callback. - Add shared tests and fixtures for callback forwarding and preload outcome tracking. This PR adds no public API and no polling abstraction. Format-specific PRs install lifecycle tracking when an ad is polled, without synthesizing another loaded event. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - Full-stack detekt and API validation <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Internal callback wrapper and unit tests only; analytics boundary matches existing load tracking with no new public surface. > > **Overview** > Adds the shared **preload** tracking layer for Next-Gen AdMob so buffer fills are recorded before any app callback runs. > > **`TrackingPreloadCallback`** wraps Google’s `PreloadCallback`: it emits RevenueCat **loaded** / **failed-to-load** events (via the same helpers as direct loads) on `onAdPreloaded` and `onAdFailedToPreload`, then forwards to an optional delegate. **`onAdsExhausted`** is forwarded only—no ad event. > > Introduces **`PreloaderTest`** with `Purchases`/`AdTracker` setup, preload assertions, and a recording delegate for follow-on format-specific preloader tests. **`TrackingPreloadCallbackTest`** covers ordering (track before delegate), exhaustion passthrough, and null delegates. > > No public API; polling/lifecycle tracking for adopted ads is left to later PRs. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 7a71e67. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Banner preload completion and banner adoption happen at different times. Tracking should record the former once while still installing lifecycle tracking when the app polls the buffered banner. This PR is stacked on #3995. ### Description - Add `BannerAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` to install banner event and refresh callbacks without recording another loaded event. - Keep preload-time and poll-time placements independent. - Continue using Google's normal `AdView.registerBannerAd` after polling. - Document both tracked and untracked banner preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Adds optional tracking wrappers around Google's preloader APIs without changing core Purchases or auth; misconfiguration mainly affects ad analytics completeness, not app security or purchases. > > **Overview** > Adds RevenueCat ad-event tracking for Google Mobile Ads Next-Gen **banner preloading**, split across preload start and later poll/adopt. > > **`BannerAdPreloader.startAndTrack`** wraps Google's `start` with `TrackingPreloadCallback` so preload success and failure are recorded once (with an optional **start-time** placement), while still forwarding all Google preload callbacks. > > **`pollAndTrackAd`** polls the buffer and installs `TrackingBannerAdEventCallback` / `TrackingBannerAdRefreshCallback` for display, click, revenue, and refresh—using a separate optional **poll-time** placement—**without** emitting another Ad Loaded event. Apps still register the banner via Google's `AdView.registerBannerAd`. > > Public API is exposed under `RCAdMobNextGenPreloaders` (experimental, Kotlin-only). README documents the two-stage placement model and the case where preloading used Google's plain `start` (lifecycle tracking only, no retroactive load). > > Unit tests cover preload tracking, null poll, and poll-time lifecycle/refresh with independent placement. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c7e45c4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
📸 Snapshot Test621 unchanged
🛸 Powered by Emerge Tools |
…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 -->
### Motivation Interstitial preload completion should be tracked when Google fills the buffer, while polling should only adopt the ad and prepare its later lifecycle tracking. ### Description - Add `InterstitialAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` to install the interstitial event callback without recording another loaded event. - Keep preload-time and poll-time placements independent. - Preserve the existing show-time placement override. - Document both tracked and untracked interstitial preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive adapter extensions and tests only; no changes to core purchases, auth, or payment logic. > > **Overview** > Adds **interstitial preloading** to the AdMob Next-Gen adapter, matching the existing banner preload flow: preload success/failure is tracked at start, and polling only wires lifecycle tracking without a second loaded event. > > **`InterstitialAdPreloader.startAndTrack`** wraps Google's `start` with `TrackingPreloadCallback` for `AdFormat.INTERSTITIAL`, forwarding all preload callbacks. **`pollAndTrackAd`** polls the buffer and sets `TrackingInterstitialAdEventCallback` on the ad; preload-time and poll-time placements stay independent, and show-time placement override behavior is unchanged. > > Public API surface is updated in `api.txt`. README documents the interstitial preload + poll + show flow. Unit tests cover start tracking, null poll, and poll lifecycle wiring (including no duplicate `trackAdLoaded` on poll). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit fbba7ca. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation App-open preload completion should be recorded while Google fills the buffer, independently from adopting and later showing the buffered ad. ### Description - Add `AppOpenAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` to install app-open lifecycle tracking without recording another loaded event. - Keep preload-time and poll-time placements independent while retaining the show-time override. - Document both tracked and untracked app-open preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive adapter extensions and tests; no changes to core purchases, auth, or payment flows. > > **Overview** > Adds **app-open preloading** to the AdMob Next-Gen adapter, matching the existing banner and interstitial preload APIs. > > `AppOpenAdPreloader.startAndTrack` wraps Google's `start` with `TrackingPreloadCallback` so preload success and failure are recorded (with an optional start-time `placement`) while still forwarding Google's preload callbacks. `pollAndTrackAd` polls the buffer, installs `TrackingAppOpenAdEventCallback` for show/click/revenue lifecycle, and **does not** emit a second loaded event—that load is attributed at preload time. Poll-time `placement` stays independent from start-time placement. > > Public API surface is updated in `api.txt`, README documents the app-open preload flow, and unit tests cover preload tracking, null poll, and lifecycle wiring without duplicate load tracking. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5d83402. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Rewarded preload completion should be recorded while Google fills the buffer, independently from adopting and later showing the buffered ad. ### Description - Add `RewardedAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` to install rewarded lifecycle tracking without recording another loaded event. - Keep preload-time and poll-time placements independent while retaining the show-time override. - Document both tracked and untracked rewarded preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Follows the existing interstitial/banner preloader extension pattern with no new security or data paths beyond ad event tracking. > > **Overview** > Extends the AdMob Next-Gen preloader tracking pattern to **rewarded** ads: `RewardedAdPreloader.startAndTrack` records preload success/failure via `TrackingPreloadCallback` (`AdFormat.REWARDED`), and `pollAndTrackAd` attaches `TrackingRewardedAdEventCallback` for show/click/revenue without emitting a second loaded event. > > Public API surface in `RCAdMobNextGenPreloaders` and the README now document the rewarded preload → poll → show flow (including independent start vs poll placements). Unit tests cover preload wiring, null poll, and lifecycle callback installation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f8ec3e4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Rewarded-interstitial preload completion should be recorded while Google fills the buffer, independently from adopting and later showing the buffered ad. ### Description - Add `RewardedInterstitialAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` to install rewarded-interstitial lifecycle tracking without recording another loaded event. - Keep preload-time and poll-time placements independent while retaining the show-time override. - Document both tracked and untracked rewarded-interstitial preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive adapter extensions parallel to existing rewarded/interstitial preloaders; no changes to core purchase or auth logic. > > **Overview** > Extends the AdMob Next-Gen preloader API so **rewarded-interstitial** ads follow the same two-stage RevenueCat tracking as other full-screen formats. > > **`RewardedInterstitialAdPreloader.startAndTrack`** wraps Google’s preload `start` with `TrackingPreloadCallback` (`AdFormat.REWARDED_INTERSTITIAL`), recording preload success/failure and forwarding Google callbacks. **`pollAndTrackAd`** polls the buffer, installs `TrackingRewardedInterstitialAdEventCallback` for show/click/revenue lifecycle, and deliberately does **not** emit a second loaded event when the ad is adopted. > > Preload-time and poll-time **placements** stay independent. Public API surface is updated in `api.txt`; README documents the rewarded-interstitial preload flow. Unit tests cover preload tracking, null poll, and lifecycle callback wiring without duplicate load tracking. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 72a1c75. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Motivation Native preloading can return native, custom-native, or banner fallback results. Preload completion should be recorded once, while polling should install the lifecycle callbacks appropriate for the returned result. ### Description - Add `NativeAdPreloader.startAndTrack` to record every preload success or failure and forward all Google callbacks. - Add `pollAndTrackAd` without recording another loaded event. - Install native loader and event callbacks on native and custom-native results. - Install banner event and refresh callbacks on banner fallback results. - Keep preload-time and poll-time placements independent. - Document both tracked and untracked native preloading flows. If preloading starts through Google's plain API, polling can track later lifecycle events but cannot retroactively report the original load. ### Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > New adapter extension following established preloader tracking patterns; no auth or payment paths touched. > > **Overview** > Adds RevenueCat tracking for **Google Mobile Ads Next-Gen native ad preloading**, matching the other preload integrations. > > `NativeAdPreloader.startAndTrack` records preload success and failure via `TrackingPreloadCallback` (`AdFormat.NATIVE`) and forwards Google preload callbacks. `pollAndTrackAd` adopts a buffered result and wires lifecycle tracking **without** emitting a second loaded event (load is attributed at start). > > Unlike single-ad-type preloaders, Google's native buffer can return **native**, **custom-native**, or **banner** success variants. `pollAndTrackAd` branches on `NativeAdLoadResult` and installs `TrackingNativeAdEventCallback` on native/custom-native ads, or banner event plus refresh callbacks when the fallback is a banner. > > Preload-time and poll-time **placements stay independent**, consistent with the rest of the module. Public API surface and README native preloading examples are updated; unit tests cover start tracking, null poll, and all three poll result types. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit b29ee9a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary - add an internal AdLoadResult helper that shares loaded/failed tracking for suspending Google Mobile Ads Next-Gen APIs - configure successful ads after the tracking attempt while returning the exact original result instance - preserve lazy response-info lookup and tracking-failure resilience, with focused success/failure contract tests ## Context This is a foundation-only change for later adoption by the format-specific suspending loaders. ## Test plan - ./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest - ./gradlew detektAll - ./scripts/api-check.sh <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Internal tracking helper plus unit tests; no public API or production call-site changes yet. > > **Overview** > Adds `AdLoadResult.trackAndConfigureAdLoadResult` so suspending Next-Gen loaders can reuse the same loaded/failed tracking as `TrackingAdLoadCallback`. > > On success it tracks then runs `configureAd`; on failure it only tracks. It always returns the original result instance, and tracking still goes through `trackIfConfigured` so missing SDK setup or tracker errors do not skip ad configuration. Unit tests cover success, failure, and those resilience cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1690bc2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
<!-- Thank you for contributing to Purchases! Before pressing the "Create Pull Request" button, please provide the following: --> ### Checklist - [x] If applicable, unit tests - [ ] If applicable, create follow-up issues for `purchases-ios` and hybrids ### Motivation Core emits reward-verification events only when an adapter supplies tracking metadata, while the next-generation AdMob adapter currently polls without it. ### Description Passes ad metadata from RevenueCat tracking callbacks into reward-verification polling with `AdCaptureMethod.ADAPTER`, preserving the latest placement while leaving untracked ads unchanged. Tests cover tracked and untracked metadata handoff, adapter capture attribution, and runtime forwarding; validated with the full next-gen unit-test suite, `detektAll`, and `scripts/api-check.sh`. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the reward-verification poll path and when analytics events fire, but verification outcomes stay the same and untracked ads are unchanged. > > **Overview** > Next-gen AdMob **reward verification polling** now supplies **`RewardedAdTrackingMetadata`** when the ad was loaded through RevenueCat’s tracking APIs, so core can emit reward-verification analytics the same way it does when adapters provide metadata. > > On reward earned, **`RewardVerificationManager`** reads metadata from the installed **`TrackingRewardedAdEventCallback`** / **`TrackingRewardedInterstitialAdEventCallback`** (null for untracked ads). **`RewardVerificationRuntime`** forwards that metadata into **`awaitPollRewardVerification`** with **`AdCaptureMethod.ADAPTER`** instead of polling without metadata / manual capture. **`TrackingAdEventCallback.rewardTrackingMetadata()`** builds the payload at reward time (current placement and response info). > > Tests assert untracked ads still pass null metadata, adapter capture method, and show-time placement for rewarded and rewarded-interstitial formats. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit de00254. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Motivation Expose Google Mobile Ads Next-Gen banner loading and event tracking through a public RevenueCat API. ## Description - Add `AdView.loadAndTrackAd` and `AdTracker.loadAndTrackBannerAd` for request-based loading through the recommended Next-Gen `AdView` API. - Track successful and failed loads while preserving Google callbacks. - Install lifecycle and refresh tracking before a loaded banner reaches application code. - Track automatic refresh successes and failures against the current creative and original load placement. - Add tracking-safe setters for banner event and refresh callbacks. - Publish and document the experimental Kotlin API. ## Tracking behavior The load-time placement is used throughout the banner lifecycle, including automatic refreshes. Banner display is tracked from `onAdImpression`; full-screen overlay callbacks are forwarded without also counting a display. RevenueCat tracking runs before application callbacks. Assigning `adEventCallback` or `bannerAdRefreshCallback` directly after a tracked load replaces the RevenueCat wrapper. Applications should provide callbacks while loading or use the tracking-safe setters. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive experimental API on the AdMob Next-Gen adapter with tests; main integration risk is apps bypassing tracking-safe setters and dropping analytics. > > **Overview** > Adds **RevenueCat ad tracking for Google Mobile Ads Next-Gen banners** loaded through `AdView`, complementing the existing banner preloader helpers. > > New entry points are **`AdView.loadAndTrackAd`** and **`AdTracker.loadAndTrackBannerAd`**. They wrap `AdView.loadAd` with `TrackingAdLoadCallback`, install lifecycle and refresh wrappers on the loaded `BannerAd` before the app’s load callback runs, and emit load success/failure (including placement and ad unit) while still forwarding Google callbacks. > > **`BannerAd.setTrackingAdEventCallback`** and **`setTrackingBannerAdRefreshCallback`** let apps swap delegates without replacing RevenueCat’s wrappers. Shared **`installTrackingCallbacks`** is extracted so **`BannerAdPreloader.pollAndTrackAd`** uses the same path as direct loads. > > Public **`api.txt`** and README cover banner placement (sticky for refreshes), usage examples, and the warning not to assign callbacks directly. **`BannerAdFlowTest`** covers load tracking order, safe callback replacement, and failed-load attribution. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2fb60b8. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Motivation Expose server-to-server Google Mobile Ads Next-Gen banner response loading through the RevenueCat tracking API. This PR is stacked on #4014, which provides the core banner tracking integration and callback installation. ## Description - Add callback-based `AdView.loadAndTrackAdFromResponse` and `AdTracker.loadAndTrackBannerAdFromResponse` APIs. - Add suspending overloads that return the original Google `AdLoadResult` unchanged. - Require an explicit ad unit ID so successful and failed response loads retain attribution. - Install lifecycle and refresh tracking before loaded banners reach application code. - Preserve application callbacks while tracking load success and failure. - Publish and document the experimental response-loading API. ## Tracking behavior The supplied placement and ad unit ID are used for response load events and subsequent banner lifecycle events. RevenueCat tracking and callback installation happen before application callbacks or suspending results are returned. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Additive experimental API on top of existing banner tracking patterns, with focused unit tests and no changes to core purchase or auth flows. > > **Overview** > Adds **server-to-server banner loading** with RevenueCat tracking for Google Mobile Ads Next-Gen, matching the existing `FromResponse` APIs on other formats. > > New public entry points are `AdView.loadAndTrackAdFromResponse` and `AdTracker.loadAndTrackBannerAdFromResponse`, each with callback and suspending overloads. Callers must pass an explicit **`adUnitId`** (and optional placement) so load success and failure events keep correct attribution when the opaque response does not expose it. > > Implementation wires `AdView.loadFromAdResponse` through the same tracking path as request-based banner loads: `TrackingAdLoadCallback` for callbacks, `trackAndConfigureAdLoadResult` for suspend, with lifecycle and refresh wrappers installed before app code sees the loaded `BannerAd`. Suspending APIs return Google’s `AdLoadResult` unchanged. > > README and `api.txt` document the new surface; `BannerAdResponseFlowTest` covers callback ordering, failure attribution, and suspending success/failure behavior. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9a172fc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Motivation Expose Google Mobile Ads Next-Gen single native-ad loading through a public RevenueCat tracking API. ## Description - Add callback-based and suspending `AdTracker.loadAndTrackNativeAd` APIs for request-based loading through Google's Next-Gen `NativeAdRequest` API. - Add callback-based `loadAndTrackNativeAdFromResponse` for server-to-server ad responses, with an explicit ad unit ID for attribution. - Track and configure every result a native request can produce: standard native, custom-native, banner, and failure results. - Install lifecycle and paid-event tracking before a loaded ad reaches application code or is emitted downstream. - Add tracking-safe callback setters for standard native, custom-native, and banner results. - Publish and document the experimental Kotlin API. ## Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest --tests 'com.revenuecat.purchases.admob.nextgen.NativeAdFlowTest' --tests 'com.revenuecat.purchases.admob.nextgen.NativeAdResponseFlowTest'` - `./gradlew :feature:admob-next-gen:lintDefaultsDebug` - `./gradlew detektAll` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > New experimental public APIs affect ad revenue attribution and event ordering; incorrect callback assignment could drop tracking, but patterns mirror existing formats and are covered by tests. > > **Overview** > Adds **RevenueCat ad tracking** for Google Mobile Ads Next-Gen **native loads**, covering direct `NativeAdRequest` loads, suspending loads, and **server-to-server** responses via new `AdTracker.loadAndTrackNativeAd` / `loadAndTrackNativeAdFromResponse` APIs (published in `api.txt`). > > Each `NativeAdLoadResult` path is handled explicitly: standard and custom-native ads get native-format load events and `TrackingNativeAdEventCallback`; banner results from a native request are tracked as **banner** with the usual banner wrappers. Tracking is installed **before** the app’s load callback runs; `TrackingNativeAdLoaderCallback` now invokes configure hooks for all three success types. > > Introduces **`NativeAdExtensions`** with `setTrackingAdEventCallback` on `NativeAd` and `CustomNativeAd` so apps can swap delegates without stripping RC wrappers. **Native preloader** `pollAndTrackAd` is refactored to reuse the same `installTrackingEventCallback` / `installTrackingCallbacks` helpers instead of duplicated private setup. > > README gains native placement guidance, usage examples, and supported-format table entries; **unit tests** cover callback/suspend/response flows and tracking-safe setters. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f3f4046. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Motivation Complete the Google Mobile Ads Next-Gen native-ad integration by supporting Google's multiple-ad loading APIs. ## Description - Add callback-based `AdTracker.loadAndTrackNativeAds` for batches of up to five ads. - Add a suspending overload that returns Google's original `Flow<NativeAdLoadResult>`. - Track and configure every standard native, custom-native, banner, or failure result before forwarding it. - Forward `NativeAdLoaderCallback.onAdLoadingCompleted` after the batch finishes. - Publish and document the experimental batch-loading APIs. ## Validation - `./gradlew :feature:admob-next-gen:testDefaultsDebugUnitTest --tests 'com.revenuecat.purchases.admob.nextgen.NativeAdBatchFlowTest'` - `./scripts/api-check.sh` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Experimental admob-next-gen extension APIs that reuse existing native tracking helpers; no auth or core purchase-path changes. > > **Overview** > Adds **batch native ad loading** on `AdTracker` for Google Mobile Ads Next-Gen, mirroring the existing single-ad `loadAndTrackNativeAd` behavior across multiple results in one request. > > New **`loadAndTrackNativeAds`** overloads accept `maxNumberOfAds` and either a **`NativeAdLoaderCallback`** or a suspending **`Flow<NativeAdLoadResult>`**. Each standard native, custom-native, banner success, or failure is **tracked and configured** (RevenueCat load/fail events plus tracking event callbacks on ads) before it reaches the app callback or flow collector; the callback path also forwards **`onAdLoadingCompleted`** when the batch finishes. > > The experimental public API is reflected in **`api.txt`**, with **`NativeAdBatchFlowTest`** covering callback forwarding and flow collection. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3cc9849. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Scaffolds a focused example app for RevenueCat tracking with Google Mobile Ads Next-Gen: - add the next-generation AdMob sample module and project wiring - initialize RevenueCat and Google Mobile Ads for the sample - add navigation for the format-focused examples ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > New isolated example module with no changes to core SDK behavior; format demos are stubs only. > > **Overview** > Adds a new **`examples/admob-next-gen-sample`** Compose app and registers it in **`settings.gradle.kts`**, aimed at demonstrating RevenueCat ad-event tracking with the **Google Mobile Ads Next-Gen SDK** via `:purchases` and `:feature:admob-next-gen`. > > The scaffold wires **RevenueCat** and **Mobile Ads** initialization in `MainApplication` (with home-screen readiness state), reads **`REVENUECAT_API_KEY`** and optional AdMob IDs from root **`local.properties`** into **`BuildConfig`**, and provides a home screen that navigates to per-format routes (banner, interstitial, app open, rewarded, native, diagnostics). **Format screens are placeholders** for follow-up work; shared UI (`AdScreen` Direct/Preloaded chips, `StatusCard`, `ActionRow`) and a **native ad layout** are included for later layers. The README documents local run steps and the intended tracking coverage. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0e2d679. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Expands the Google Mobile Ads Next-Gen example app with reusable preloader controls: - add reusable controls for starting and stopping format preloaders - expose buffer capacity and available-ad state - keep buffer configuration editable only before a preloader starts ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Sample-app-only UI helpers with no changes to SDK, billing, or production ad integration paths. > > **Overview** > Adds **reusable Compose UI** for the AdMob Next-Gen sample so format screens can demo ad preloading without duplicating layout code. > > **`PreloaderUi.kt`** introduces `PreloaderUiState` (backed by `PreloadConfiguration` and an ads-available provider), `rememberPreloaderUiState`, and `PreloaderPanel`: start/stop toggle, running/stopped badge, buffer capacity steppers (1–5, locked while running), live “ads ready” count with ~500ms polling, status messages after start/stop/preload events, plus optional extra metrics and action buttons. > > **`AdSupport.kt`** adds `preloadStatusCallback`, a small `PreloadCallback` that hops preload success, failure, and buffer-exhausted events onto a `CoroutineScope` so the panel can update safely from SDK callbacks. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5868b70. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Expands the Google Mobile Ads Next-Gen example app with banner workflows: - add direct and preloaded banner workflows - reset rendered banner state when switching load modes - demonstrate registration and refresh callbacks ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Sample-app and documentation changes only; no production SDK or auth/data-path changes. > > **Overview** > Replaces the **Banner** placeholder in the AdMob Next-Gen sample with a working **Banner** screen that shows RevenueCat-tracked banner flows end to end. > > **Direct** mode loads into an `AdView` via `loadAndTrackAd`, with status for load success/failure plus **impression**, **click**, and **refresh** callbacks. **Preloaded** mode uses the shared preloader UI to **start/stop** buffering, then **poll + register** a buffered ad on the same view with distinct placements (`banner_direct`, `banner_preload`, `banner_poll`). Switching load modes recreates the `AdView` (`key(mode)`) and resets status so banner state does not leak across modes. > > The sample README **Coverage** line for banners is updated to describe lifecycle/refresh callbacks instead of callback-replacement wording. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1035d0b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Expands the Google Mobile Ads Next-Gen example app with full-screen ad workflows: - add direct and preloaded interstitial examples - add direct and preloaded app-open examples - demonstrate show-time placement overrides ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are confined to the example app’s UI and documentation-style ad integration; no SDK or production code paths are modified. > > **Overview** > Replaces **interstitial** and **app open** placeholders in the AdMob Next-Gen sample with real Compose flows wired through RevenueCat’s `adTracker` and Next-Gen preload helpers. > > Adds a shared **`FullScreenAdScreen`** that mirrors the banner sample’s **direct load** vs **preload** modes: tracked load (`loadAndTrackInterstitialAd` / `loadAndTrackAppOpenAd`), `startAndTrack` / `pollAndTrackAd`, show/dismiss handling, and status UI. Each format uses **different placement strings** at load, poll, preload, and show so reviewers can see **show-time placement overrides** (called out explicitly on the interstitial screen). App-open loading is **manual** in the sample rather than tied to process lifecycle. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e773e0c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Expands the Google Mobile Ads Next-Gen example app with native ad workflows: - add direct native loading for single and batched ads - add native preloading with configurable poll-and-render counts - replace previously rendered native ads when loading again ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are confined to the example app and documentation, with no production SDK or billing/auth impact. > > **Overview** > Replaces the **Native** placeholder in the AdMob Next-Gen sample with a full **`NativeScreen`** that exercises RevenueCat’s native tracking APIs end to end. > > Users can switch between **standard and native-video test ad units** (new `ADMOB_NATIVE_VIDEO_AD_UNIT_ID` in `local.properties` / `BuildConfig`), then try **direct** loads via `loadAndTrackNativeAd` or a **batch `Flow`** via `loadAndTrackNativeAds`, or **preloaded** inventory with start/stop, buffer sizing, and **Poll + Show** using `startAndTrack` / `pollAndTrackAd`. Loaded ads render in `NativeAdView` cards; non-native result types are tracked and destroyed with explicit status messaging, and in-flight loads clean up SDK-owned ads on cancel or mode changes. > > **`AdScreen`** gains an optional **`onModeChange`** hook so switching Direct/Preloaded clears native UI state; README coverage notes mention standard/video selection. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 80f65d6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Expands the Google Mobile Ads Next-Gen example app with rewarded-ad and diagnostics workflows: - add direct and preloaded rewarded-ad workflows - demonstrate optional RevenueCat reward verification - add intentional direct and preload failures for diagnostics ## Screenshots   <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Example-app UI only; no SDK or production code changes. > > **Overview** > Replaces placeholder routes in the AdMob Next-Gen sample with **Rewarded** and **Diagnostics** screens wired through `SampleUi`. > > **Rewarded** adds the same direct-vs-preloaded flow as other formats: load/show via `loadAndTrackRewardedAd`, preloader `startAndTrack` / `pollAndTrackAd`, and a toggle to enable RevenueCat reward verification on load/poll (verification vs Google’s `OnUserEarnedRewardListener` at show time). > > **Diagnostics** deliberately uses an invalid interstitial ad unit to trigger expected load failures and surface RevenueCat failed-to-load tracking for both direct load and interstitial preloading. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d3604f1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Completes the Google Mobile Ads Next-Gen example app with rewarded-interstitial support and regression coverage: - add direct and preloaded rewarded-interstitial workflows - demonstrate optional RevenueCat reward verification - add focused regression coverage for placement override callback handling ## Screenshots  <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to the example app, documentation, and unit tests; no production adapter behavior is modified in this diff. > > **Overview** > Finishes the AdMob Next-Gen **sample app** by replacing the rewarded-interstitial placeholder with a real screen and documenting how to run and validate every format. > > **Rewarded UI** is refactored into a shared generic `RewardedAdScreen` used by both **Rewarded** and **Rewarded interstitial**. Each wires RevenueCat **direct load**, **preload/poll**, and **show** with format-specific placements, plus an optional **RevenueCat reward verification** toggle (applied at load/poll time). > > **Docs** cross-link the sample and adapter READMEs and add guidance on direct vs preloaded flows, dashboard event checks, native test inventory caveats, and main-thread handling for SDK callbacks. > > **Tests:** new `PlacementOverrideTest` covers `applyPlacementOverride` on tracking callbacks (update, clear, ignore non-tracking wrappers). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 44b412b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## Summary Adds CI coverage for the Google Mobile Ads Next-Gen example app: - add a Fastlane lane that assembles the Next-Gen AdMob sample - add a dedicated CircleCI job for the sample build - run the job in pull-request and main-branch snapshot workflows <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to Fastlane and CircleCI wiring; no SDK, auth, or runtime behavior is modified. > > **Overview** > Adds **CI coverage** for the Google Mobile Ads Next-Gen example (`examples:admob-next-gen-sample`), mirroring the existing AdMob integration sample pipeline. > > A new Fastlane lane **`build_admob_next_gen_integration_sample`** assembles the debug APK via `:examples:admob-next-gen-sample:assembleDebug`. CircleCI gets a matching **`assemble-admob-next-gen-integration-sample-app`** job (same setup as the current AdMob sample job), wired into **`build-test-deploy`** after `prepare-tests` and into **`snapshot-deploy-sample-app-tests`** on main alongside the legacy AdMob sample build. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 274017e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
### Description Adds a RevenueCat-specific migration guide for moving from the legacy `purchases-admob` adapter to `purchases-admob-next-gen`. ### Changes - Documents the dependency and package/import replacements. - Explains required Next-Gen initialization and mediation exclusions. - Maps legacy tracking entry points and callback types to their Next-Gen equivalents. - Clarifies why banners use `BannerAdRequest` instead of a generic `AdRequest`. - Covers callback threading, tracking-safe callback replacement, reward verification, and placement continuity. ### Testing - `git diff --check` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only README update with no runtime, API, or build changes. > > **Overview** > Adds a **Migrate from the legacy adapter** section to `feature/admob-next-gen/README.md`, positioned before the existing initialization docs. > > The new guide states that `purchases-admob-next-gen` is not a drop-in replacement and walks integrators through swapping legacy `purchases-admob` and `play-services-ads` for the Next-Gen adapter and `ads-mobile-sdk`, with a pointer back to mediation exclusions. It documents package/import moves (`com.revenuecat.purchases.admob` → `nextgen`, GMS ads → `com.google.android.libraries.ads.mobile.sdk`) and ties initialization to `InitializationConfig` and the existing initialize section. > > A format-by-format table maps legacy vs Next-Gen RevenueCat tracking entry points (notably banners via `BannerAdRequest`, native via `AdTracker.loadAndTrackNativeAd(s)` instead of `AdLoader.Builder.forNativeAdWithTracking`). The text explains request/callback type changes (`AdLoadCallback`, format-specific event callbacks), keeping `placement` and reward-verification helpers, background-thread callbacks with main-thread UI updates, and using RevenueCat load helpers / `setTrackingAdEventCallback` so tracking wrappers are not replaced accidentally. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1d30f8a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
# Conflicts: # .circleci/config.yml
There was a problem hiding this comment.
Now that it's all put together this README is way too long and hard to follow. It could do with a table of contents at least, but a bunch of the header nesting really doesn't make sense
In general the sensation is, like some of the code, that we're explaning the same thing 6 times with different ad types rather than one cohesive API (placements, callback warnings, etc are repeated all over the place)
Checklist
purchases-iosand hybrids are not applicable because this adapter targets the Android-only Google Mobile Ads SDKMotivation
The existing
purchases-admobadapter targets the legacy Google Mobile Ads SDK and cannot be used with Google’s Next-Gen SDK.This PR introduces a separate adapter for applications migrating to Google Mobile Ads Next-Gen while preserving RevenueCat ad-event tracking across supported formats.
Description
This PR adds the publishable
purchases-admob-next-genartifact and its complete tracking integration.All changes in this integration PR were reviewed and approved in their individual PRs before being integrated into this branch.
Adapter support
Distribution and compatibility
com.revenuecat.purchases:purchases-admob-next-gen.purchases-admob-next-genmutually exclusive with the legacypurchases-admobadapter and legacy Google Mobile Ads artifacts.Documentation and sample
CI and release integration
Validation
Note
Medium Risk
New published SDK surface and ad analytics plumbing tied to Google’s Next-Gen APIs; mistakes could mis-attribute events or break when mixed with the legacy AdMob stack, but core purchase/auth paths are untouched.
Overview
Introduces
purchases-admob-next-gen, a new RevenueCat adapter for Google’s Mobile Ads Next-Gen SDK (alongside the legacypurchases-admobartifact, which apps must not combine with it). The module wires RevenueCat ad-event tracking into Next-Gen load, show, preload, native batch Flow, server-to-server responses, placement overrides, and reward verification, with callback wrappers that forward app code after tracking.Distribution and validation updates include adding the artifact to the BOM, a Compose sample (
admob-next-gen-sample) that exercises every format, a large adapter README with migration guidance, and CircleCI/Fastlane jobs to assemble the new sample.Reviewed by Cursor Bugbot for commit 1552ef5. Bugbot is set up for automated code reviews on this repo. Configure here.