Skip to content

Introduce Tracking namespace and extract delegate stores#6681

Merged
peterporfy merged 28 commits into
mainfrom
admob-tracking-namespace-enum
May 5, 2026
Merged

Introduce Tracking namespace and extract delegate stores#6681
peterporfy merged 28 commits into
mainfrom
admob-tracking-namespace-enum

Conversation

@polmiro

@polmiro polmiro commented Apr 23, 2026

Copy link
Copy Markdown
Member

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

The adapter had a flat Sources/RevenueCatAdMob/ directory with RC*/RCAdMob*-prefixed types and a RCAdMob god-class mixing the singleton, event forwarding, associated-object retention, and orchestration. The RC* prefixes are Obj-C naming conventions that add noise in an internal-only Swift adapter.

Description

  • Introduce a Tracking namespace (enum Tracking {}) and move all adapter sources into Sources/RevenueCatAdMob/Tracking/
  • Rename RCAdMobTracking.Adapter, RCAdMob* delegates/helpers → Tracking.*
  • Consolidate all associated-object retention behind a single generic Tracking.AssociatedObjectStore<Value: AnyObject>; per-format stores are typealiases
  • Collapse three banner stores into a single BannerTrackingState object per banner, eliminating repeated lookups and the explicit "already wrapped" flag
  • Rename test files/classes to mirror the new layout; add AssociatedObjectStoreTests

Public API (loadAndTrack(...), present(from:placement:...), setTrackingFullScreenContentDelegate(_:)) and all behavior are unchanged.

Testing

  • swift build clean
  • Full adapter test suite passing (xcodebuild test -scheme RevenueCatAdMob)
  • swiftlint clean

@polmiro polmiro requested a review from a team as a code owner April 23, 2026 13:07
@polmiro polmiro changed the title Refactor RevenueCatAdMob: introduce Tracking namespace and extract delegate stores Introduce Tracking namespace and extract delegate stores Apr 23, 2026
@polmiro

polmiro commented Apr 23, 2026

Copy link
Copy Markdown
Member Author

@RCGitBot please test

@polmiro polmiro requested review from a team and ajpallares April 23, 2026 13:22
polmiro added a commit that referenced this pull request Apr 23, 2026
Mirror the caseless-enum namespace pattern that
admob-tracking-namespace-enum (#6681) introduced for the `Tracking`
subsystem.

- New `RewardVerification.swift` declares `internal enum RewardVerification {}`
  as the umbrella for the post-reward SSV poll subsystem.
- All six leaf types from the previous commit are now nested under
  `RewardVerification` and lose the redundant prefix:
    `RewardVerificationDispatcher`              -> `RewardVerification.Dispatcher`
    `RewardVerificationPoller`                  -> `RewardVerification.Poller`
    `RewardVerificationOutcome`                 -> `RewardVerification.Outcome`
    `RewardVerificationState`                   -> `RewardVerification.State`
    `RewardVerificationStateStore`              -> `RewardVerification.StateStore`
    `RewardVerificationSetup`                   -> `RewardVerification.Setup`
  Plus the supporting types:
    `RewardVerificationPollResult`              -> `RewardVerification.PollResult`
    `RewardVerificationStatusPolling`           -> `RewardVerification.StatusPolling`
    `RewardVerificationCapableAd`               -> `RewardVerification.CapableAd`
    `RewardVerificationJitter`                  -> `RewardVerification.Jitter`
    `PurchasesRewardVerificationStatusPoller`   -> `RewardVerification.PurchasesStatusPoller`
    `AsyncSleeper` / `TaskSleeper`              -> `RewardVerification.AsyncSleeper` / `.TaskSleeper`
- Source files drop the prefix to mirror the namespace:
    `RewardVerificationDispatcher.swift`        -> `Dispatcher.swift`
    `RewardVerificationPoller.swift`            -> `Poller.swift`
    `RewardVerificationOutcome.swift`           -> `Outcome.swift`
    `RewardVerificationState.swift`             -> `State.swift`
    `RewardVerificationStateStore.swift`        -> `StateStore.swift`
    `RewardVerificationSetup.swift`             -> `Setup.swift`
- `RewardVerification.StateStore` collapses the previous nested
  `AssociatedKeys` enum into a `private static var key: UInt8`,
  matching the pattern used by `Tracking.FullScreenDelegateStore` /
  `Tracking.NativeDelegateStore` / `Tracking.NativeAdLoaderProxyStore`.
  This also satisfies SwiftLint's nesting rule (max 1 level deep).
- Test classes mirror production: `RewardVerificationPollerTests` ->
  `PollerTests`, `RewardVerificationOutcomeTests` -> `OutcomeTests`,
  etc. The polling-loop test factory `makeRewardVerificationPoller`
  is now just `makePoller`.

No behavior changes. The GMA conformances of `GoogleMobileAds.RewardedAd`
and `GoogleMobileAds.RewardedInterstitialAd` to `RewardVerification.CapableAd`
still live next to the protocol they conform to (in `Setup.swift`), but
hang off file scope rather than inside the namespace extension because
Swift forbids declaring conformances inside an `extension` of a different
type.

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

Nice refactor! I need to look deeper into this, but I have a couple of initial suggestions

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a73c8e2. Configure here.

@polmiro polmiro force-pushed the rewardverification-poll-reward-payload branch from 38c43d0 to 86bb476 Compare April 24, 2026 10:14
@polmiro polmiro force-pushed the admob-tracking-namespace-enum branch from 910f4cc to 6cfa59a Compare April 24, 2026 10:16
@polmiro

polmiro commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@RCGitBot please test

@polmiro polmiro requested a review from ajpallares April 24, 2026 10:54
Base automatically changed from rewardverification-poll-reward-payload to main April 24, 2026 14:33
polmiro added 12 commits April 24, 2026 16:41
Plumbs the verified-reward payload from the backend response through the
internal `pollRewardVerificationStatus` SPI so RC ad adapters can dispatch
meaningful "verified" events with the granted reward.

* Adds `VirtualCurrencyReward` and `VerifiedReward` (`@_spi(Internal) public`)
  under `Sources/Ads/RewardVerification/`.
* Extends `RewardVerificationStatusResponse` to optionally decode a typed nested
  reward payload (`{ type, code, amount }`) on `verified` responses.
  - `reward` absent or null → `.noReward`
  - `type == "virtual_currency"` with valid `code`+`amount` → `.virtualCurrency`
  - any other `type` (or malformed `virtual_currency`) → `.unsupportedReward` + warn
* `RewardVerificationPollStatus.verified` now carries the `VerifiedReward`.
* `Purchases.pollRewardVerificationStatus` returns `.verified(reward)`.
* Test mocks injected via a new `BasePurchasesTests.MockBackend` convenience
  init so `backend.adsAPI` resolves to `MockAdsAPI`.
Previously, `decodeVerifiedReward` silently returned `.noReward` for any
of: missing reward key, null reward, or non-object reward value (e.g. a
string/number/array). The first two are expected; the last indicates a
backend mismatch and should be surfaced.

Now distinguish:
- absent or null reward → `.noReward` (silent, expected)
- present but not a JSON object → `.unsupportedReward` + `Logger.warn`

This matches the existing convention in this file of logging warnings
when decoding unrecognized/malformed values into fallback cases.
Align reward payload modeling with backend semantics by decoding `amount` as Int and updating tests to reject fractional values as malformed rewards.
…apter

First step in regrouping the AdMob adapter under a single namespace.

Internal-only refactor — no public API changes:
* New `Tracking` umbrella enum (caseless) holds `Adapter`, `Tracker`,
  `PurchasesTracker`.
* `RCAdMob` class → `Tracking.Adapter`; its file is split into
  `Tracking/{Tracking,Adapter,Tracker,PurchasesTracker}.swift`.
* `AdTracking` protocol → `Tracking.Tracker`.
* `PurchasesAdTracker` → `Tracking.PurchasesTracker`.
* `rcAdMob:` parameter / `rcAdMob` property → `adapter:` / `adapter`
  across the four delegate wrappers, the GMA `loadAndTrack` extensions,
  and tests.

The four `RCAdMob*` delegate wrapper class names, `RCFullScreenAdTracking`,
and the `Tracking/` file moves of the wrappers themselves are deferred to
commit 2 for a smaller diff per step.
Move every adapter file into the Tracking/ subfolder and drop the RC*/
RCAdMob* prefixes by nesting the types inside the Tracking namespace.

File moves (git mv preserves history):
- RCAdMobBannerViewDelegate.swift          -> Tracking/BannerViewDelegate.swift
- RCAdMobFullScreenContentDelegate.swift   -> Tracking/FullScreenContentDelegate.swift
- RCAdMobNativeAdDelegate.swift            -> Tracking/NativeAdDelegate.swift
- RCAdMobNativeAdLoader.swift              -> Tracking/AdLoader+Tracking.swift
- BannerView+RCAdMob.swift                 -> Tracking/BannerView+Tracking.swift
- FullScreenAd+RCAdMob.swift               -> Tracking/FullScreenAd+Tracking.swift
- FullScreenAd+RCAdMob+Completion.swift    -> Tracking/FullScreenAd+Tracking+Completion.swift

Type renames (all internal):
- RCAdMobBannerViewDelegate         -> Tracking.BannerViewDelegate
- RCAdMobFullScreenContentDelegate  -> Tracking.FullScreenContentDelegate
- RCAdMobNativeAdDelegate           -> Tracking.NativeAdDelegate
- RCNativeAdLoaderDelegateProxy     -> Tracking.NativeAdLoaderDelegateProxy
  (kept "Proxy" suffix to avoid clashing with GoogleMobileAds.NativeAdLoaderDelegate)
- RCFullScreenAdTracking (protocol) -> Tracking.FullScreenAd
- FullScreenLoadContext (struct)    -> Tracking.FullScreenLoadContext
- rcSetTrackingFullScreenContentDelegate (helper on the protocol)
                                    -> setTrackingDelegate on Tracking.FullScreenAd
- private file-scoped key enums lose RC prefix
  (RCBannerAssociatedKeys -> BannerAssociatedKeys,
   RCNativeAdLoaderAssociatedKeys -> NativeAdLoaderAssociatedKeys)

Public API surface is unchanged: setTrackingFullScreenContentDelegate(_:),
loadAndTrack(...), and present(from:placement:...) keep their signatures and
behavior; only the internal helpers they forward to were renamed.

Test files still use their RCAdMob* filenames (deferred to commit 6); only
the type references inside them were updated.

Verified: swift build, xcodebuild test on iOS Simulator (87 tests passing),
swiftlint clean.
Move the Obj-C associated-object retention logic for the full-screen
content tracking delegate out of Tracking.Adapter into a dedicated
Tracking.FullScreenDelegateStore type that owns the associated-object key
and exposes retain/retrieve operations.

Tracking.Adapter holds an instance and exposes it directly so callers
reach for adapter.fullScreenDelegateStore.{retain,retrieve}(...) instead
of the previous retainFullScreenDelegate / retrieveFullScreenDelegate
facades on Adapter.

Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC).
Public API unchanged. Verified with iOS Simulator test run (87/87 passing).
Move the Obj-C associated-object retention logic for the native ad
tracking delegate out of Tracking.Adapter into a dedicated
Tracking.NativeDelegateStore type that owns the associated-object key
and exposes the retain operation.

Tracking.Adapter holds an instance and exposes it directly so callers
reach for adapter.nativeDelegateStore.retain(...) instead of the previous
retainNativeDelegate facade on Adapter.

Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC).
Public API unchanged. Verified with iOS Simulator test run (87/87 passing).
Move the Obj-C associated-object retention logic for the native
ad-loader delegate proxy out of GoogleMobileAds.AdLoader.loadAndTrack
into a dedicated Tracking.NativeAdLoaderProxyStore type that owns the
associated-object key and exposes the retain operation.

Tracking.Adapter holds an instance and exposes it directly, so the
AdLoader extension reaches for adapter.nativeAdLoaderProxyStore.retain(...)
instead of using a private file-scoped associated-object key with
objc_setAssociatedObject inline.

Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC).
Public API unchanged. Verified with iOS Simulator test run (87/87 passing).

This completes the symmetry across the three delegate stores
(FullScreenDelegateStore, NativeDelegateStore, NativeAdLoaderProxyStore),
each owning a single associated-object key and a single responsibility.
Drop the RCAdMob* prefix from all adapter test files and class names so
the test suite matches the namespacing used by the production sources
(Tracking.* in Sources/RevenueCatAdMob/Tracking/).

File and class renames:
- RCAdMobTestCase.swift                       -> AdapterTestCase.swift
  (typealias RCAdMobTestCase -> AdapterTestCase)
- RCAdMobAPISurfaceTests.swift                -> APISurfaceTests.swift
- RCAdMobBannerDelegateForwardingTests.swift  -> BannerDelegateForwardingTests.swift
- RCAdMobBannerPaidHandlerBehaviorTests.swift -> BannerPaidHandlerBehaviorTests.swift
- RCAdMobBannerWrapperBehaviorTests.swift     -> BannerWrapperBehaviorTests.swift
- RCAdMobDelegateContractTests.swift          -> DelegateContractTests.swift
- RCAdMobFullScreenDelegateForwardingTests.swift
                                              -> FullScreenDelegateForwardingTests.swift
- RCAdMobNativeAdLoaderProxyBehaviorTests.swift
                                              -> NativeAdLoaderProxyBehaviorTests.swift
- RCAdMobNativeDelegateForwardingTests.swift  -> NativeDelegateForwardingTests.swift
- RCAdMobPrecisionMappingTests.swift          -> PrecisionMappingTests.swift
- RCAdMobRevenueConversionTests.swift         -> RevenueConversionTests.swift
- RCAdMobTrackingTests.swift                  -> AdapterTrackingTests.swift
  (also renames the secondary RCAdMobDelegateTrackingTests class within
   the file to AdapterDelegateTrackingTests)

Split RCAdMobDelegateRetentionTests.swift into per-store test files,
mirroring the store extractions:
- FullScreenDelegateStoreTests.swift — exercises Tracking.Adapter
  .fullScreenDelegateStore.retain(_:for:)
- NativeDelegateStoreTests.swift     — exercises Tracking.Adapter
  .nativeDelegateStore.retain(_:for:)

No production code changes. Verified with iOS Simulator test run
(87/87 passing).
@polmiro polmiro force-pushed the admob-tracking-namespace-enum branch from e015c4d to ca17fc5 Compare April 24, 2026 14:44
@peterporfy

peterporfy commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@ajpallares fyi I will manage this and the related PRs this week while Pol is away. Let me know if there is anything else here to do - it looks good to me. Thank you!

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

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

Looking good! I have some more suggestions, mostly to simplify things a bit. Also, we should update the PR, as it seems to be a bit stale

Comment thread AdapterSDKs/RevenueCatAdMob/Sources/RevenueCatAdMob/Tracking/Adapter.swift Outdated
@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

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

I think it looks good!

Also, could you simplify the PR description? I think it's perhaps too big to read comfortably

@peterporfy

Copy link
Copy Markdown
Contributor

@ajpallares simplified the description, removed the unnecessary comment and the branch is up to date with main

@peterporfy

Copy link
Copy Markdown
Contributor

@RCGitBot please test

@peterporfy peterporfy merged commit c2f9b41 into main May 5, 2026
42 checks passed
@peterporfy peterporfy deleted the admob-tracking-namespace-enum branch May 5, 2026 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants