Skip to content

Add the CloudX First Look demo app for React Native - #1

Open
antonurankar-moloco wants to merge 3 commits into
mainfrom
tomi-rn-first-look-fill-trigger
Open

Add the CloudX First Look demo app for React Native#1
antonurankar-moloco wants to merge 3 commits into
mainfrom
tomi-rn-first-look-fill-trigger

Conversation

@antonurankar-moloco

@antonurankar-moloco antonurankar-moloco commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Public copy of the CloudX First Look reference integration for React Native, with Google Ad Manager as the fallback.

CloudX gets the first chance to fill on every ad opportunity. If CloudX does not fill, the app falls back to GAM for that opportunity only, and the next opportunity starts back at CloudX. Exactly one SDK owns the placement at any moment — the two are never loaded in parallel.

What's here

One banner and one interstitial. That is deliberate: the point is the pattern, not format coverage.

File What it is
src/config/adUnits.ts Placements, timing constants, required dashboard setup
src/firstlook/useFirstLookBanner.ts The banner refresh cycle
src/firstlook/FirstLookBannerSlot.tsx How that cycle is rendered — visible slot plus hidden preload slot
src/firstlook/useFirstLookInterstitial.ts The simpler fullscreen case
App.tsx Minimal host screen

Builds against published artifacts

Nothing resolves from source. cloudx-react-native@3.4.7 from npm, CloudXCore 3.4.5 (exact) from CocoaPods, io.cloudx:sdk:4.4.0 from Maven, React Native 0.76.2.

CloudXGoogleWaterfallAdapter is deliberately absent — it runs AdMob demand inside the CloudX auction, which is the opposite of First Look, and it pins an exact Google-Mobile-Ads-SDK version that fights react-native-google-mobile-ads.

Verified

Android 15 emulator and iPhone 17 Pro simulator (iOS 26.5), both platforms, both formats.

Happy path — CloudX initializes, the banner fills, the interstitial displays.

Fallback path — forced by pointing the slot at ad unit ids that cannot resolve. GAM filled the banner and showed the interstitial on both platforms, and the cycle returned to CloudX afterwards rather than staying on GAM:

22:26:09.784  CloudX  -> INVALID_AD_UNIT
22:26:09.8xx  GAM     -> filled, swapped in
   (REFRESH_DELAY_MS = 30s)
22:26:41.801  CloudX  -> INVALID_AD_UNIT    <- back to CloudX

That last line is the claim the whole pattern rests on: a CloudX miss costs one opportunity, not the placement.

tsc --noEmit clean; eslint 0 errors.

Known issue, not fixed here

The CloudX SDK's own banner auto-refresh is enabled on the demo placement, which this pattern
requires to be off — the app owns the refresh cycle, and a second timer swaps an ad in mid-cycle.
Observed on guDml31r4Ys6O6HroPJia:

[AdViewManager] [guDml31r4Ys6O6HroPJia] Starting auto-refresh
[AdViewManager] [guDml31r4Ys6O6HroPJia] Banner refresh scheduled in 30s

This needs a dashboard change (banner ad unit refresh rate -> 0); there is no client-side fix.
CloudXBannerAd.stopAutoRefresh() resolves the ad unit id against the programmatic overlay ads
created through that API, and a CloudXBannerView-rendered banner is not in that registry, so the
call silently does nothing. The README and adUnits.ts now state this as a requirement to be
satisfied rather than claiming it already is. The iOS placement was not checked.

Identifiers

Matched to the public Unity demo (cloudx-io/cloudx-unity) so both public demos are the same app:
io.cloudx.sample on both platforms, and Unity's CloudX app keys and placements. adUnits.ts
carries all five of Unity's formats; only banner and interstitial have consumers here.

Trade-off accepted: CloudX reporting cannot separate RN traffic from Unity traffic, and the two apps
overwrite each other on a device that has both.

The GAM ids were already identical — RN resolves them through TestIds, Unity hardcodes them.

Notes for review

  • AppDelegate.swift forces test mode unconditionally, so the app serves test creatives against the shared demo placements rather than burning real fill. Remove it for a production integration.
  • Ad unit ids and app keys here are the shared CloudX demo placements, already used by the public ObjC, Swift and Android sample apps.

Public copy of the First Look reference integration: CloudX gets the first
chance to fill every ad opportunity, and Google Ad Manager serves as the
fallback for that opportunity only.

The app builds against published release artifacts rather than any in-repo
source: cloudx-react-native 3.4.7 from npm, CloudXCore 3.4.5 from CocoaPods,
and io.cloudx:sdk 4.4.0 from Maven.

One banner and one interstitial, plus the two hooks that own the cycle:

  src/firstlook/useFirstLookBanner.ts       the refresh cycle
  src/firstlook/FirstLookBannerSlot.tsx     how that cycle is rendered
  src/firstlook/useFirstLookInterstitial.ts the fullscreen case
  src/config/adUnits.ts                     placements and timing constants

Verified on an Android 15 emulator and an iPhone 17 Pro simulator: CloudX
initializes, the banner fills, and the interstitial displays on both.
Both public First Look demos now present as the same app. The React Native
demo's identifiers were inherited artifacts of sharing placements with the
native ObjC demo, which is why a React Native project carried the bundle id
cloudx.CloudXObjCRemotePods.

  Android applicationId  io.cloudx.demo.demoapp     -> io.cloudx.sample
  iOS bundle id          cloudx.CloudXObjCRemotePods -> io.cloudx.sample

The CloudX app keys and placements switch to the ones the Unity demo uses, and
adUnits.ts now carries all five formats so it diffs cleanly against Unity's
DemoConfig. Only the banner and interstitial have consumers here; the MREC,
app-open and rewarded ids are unused and marked as such.

The GAM ids are untouched. They already resolve to the same four values Unity
hardcodes, via TestIds from react-native-google-mobile-ads.

The dashboard-refresh comments are rewritten as a requirement rather than a
claim. The previous text asserted both placements already read refresh_rate: 0,
which an emulator run disproved. Verified again after this change: the new
banner placement guDml31r4Ys6O6HroPJia also logs "Banner refresh scheduled in
30s", so the SDK timer is still racing this app's cycle. That needs a dashboard
change; there is no client-side fix, because stopAutoRefresh resolves the ad
unit id against programmatic overlay ads and a component-rendered banner is not
in that registry.

Verified on an Android 15 emulator and an iPhone 17 Pro simulator: both new
placements resolve under the new app identifier with no INVALID_AD_UNIT, the
banner fills, and the interstitial displays.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant