In-App Ad Integration Guide
Partner-facing guide for integrating the touchpoint-keyed in-app ad system. Covers preload/show lifecycle, remote config keys, layout options, and troubleshooting.
Note: Unlike the Confluence spec which mentions
native_full_2ID(2 alternating native IDs), this implementation uses a single native ID (AdUnitIdSingle). Waterfall happens between native and interstitial, not within native. PO can re-add 2ID waterfall as a v2 enhancement if measured performance requires it.
Quick Start
Step 1 — Register touchpoints in Application.onCreate
Both native and interstitial (recommended — full waterfall):
InAppAdManager.initOnce(
InAppAdConfig.Builder()
.register(
tp = "home",
native2Id = AdUnitId.AdUnitIdSingle("ca-app-pub-xxx/native"),
inter = AdUnitId.AdUnitIdSingle("ca-app-pub-xxx/inter"),
)
.build()
)
Native only (no inter fallback):
InAppAdConfig.Builder()
.register(
tp = "home",
native2Id = AdUnitId.AdUnitIdSingle("ca-app-pub-xxx/native"),
)
.build()
Interstitial only (no native):
InAppAdConfig.Builder()
.register(
tp = "home",
inter = AdUnitId.AdUnitIdSingle("ca-app-pub-xxx/inter"),
)
.build()
At least one of native2Id or inter must be non-null; an IllegalArgumentException is thrown at build time otherwise.
Step 2 — Preload at PO-assigned point
// Call this as early as possible (e.g. after splash, before the trigger screen)
InAppAdManager.preload("home")
Step 3 — Show at trigger point with onNextAction
InAppAdManager.show(activity, "home") {
// Runs after ad dismissed (or skipped) — navigate forward here
startActivity(Intent(this, HomeActivity::class.java))
}
onNextAction is always called exactly once, whether the ad shows, is skipped due to purchase/remote gate, or fails to load.
Remote Config Reference
All settings for a touchpoint are consolidated into one JSON key per touchpoint.
| Key | Type | Default |
|---|---|---|
inapp_ad_<xxx> |
JSON string | "" (all fields use defaults) |
JSON schema for inapp_ad_<xxx>
{
"enabled": true,
"native_2id": true,
"fo_layout": "na_in",
"return_layout": "na_in",
"countdown": 5
}
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable/disable interstitial ad at touchpoint |
native_2id |
bool | true |
Enable/disable native full-screen 2-ID at touchpoint |
fo_layout |
string | "na_in" |
Layout for first-open flow — one of na_in, na_re, na_preparing |
return_layout |
string | "na_in" |
Layout for return-user flow — one of na_in, na_re, na_preparing |
countdown |
long | 5 |
Countdown seconds before auto-close button appears |
If the key is absent or the JSON is malformed, all fields fall back to their defaults. Unknown extra fields are ignored (forward-compatible).
Layout Reference
na_in — Native Inline
Standard native ad layout rendered inline inside a full-screen activity. Ad content fills the visible area with a top-bar title and a countdown close button.
na_re — Native Resume
Compact resume-style layout, typically used when returning from background. Lighter visual weight; suited for non-intrusive return-user flows.
na_preparing — Native Preparing
Loading/preparing screen variant. Displays ad while the app performs background work (e.g. resource loading). The countdown timer is visible from the first frame.
Troubleshooting
-
Ad not showing — purchase gate:
AppPurchase.getInstance().isPurchasedreturnedtrue. Expected: no ad for paying users. -
Ad not showing — remote disabled: Firebase key
inapp_ad_<xxx>has"enabled": false. Check key name matches the touchpoint string exactly. -
Ad not showing — load failed: Both native and interstitial loads failed within the timeout. Check ad unit IDs and network connectivity; ForTester log will show
load_failed. -
Ad not showing — touchpoint not registered:
InAppAdManager.initOnce()was not called, or the touchpoint string passed toshow()does not match one registered inInAppAdConfig. SDK will throwIllegalStateException. -
Wrong layout shown: Firebase key
inapp_ad_<xxx>has an unrecognisedfo_layoutorreturn_layoutvalue. Accepted values:na_in,na_re,na_preparing. SDK falls back tona_in. -
Reload not triggering after dismiss: 3-second minimum display guard is active. The ad must have been visible for at least 3 s before a reload is enqueued. If the user dismisses faster, no reload fires until the next app-foreground event.
-
Filtering ForTester logs:
adb logcat | grep FOR_TESTER_FOExpected sequence for a successful impression:
request→load_start→native_loaded/inter_loaded→impression→dismissed→reload_scheduled. -
Registration checklist log — emitted once at
initOnce()time. Cross-check ad unit IDs against Firebase config at a glance:[InAppAd] ═══════ Registered 2 touchpoint(s) ═══════ [InAppAd] ✓ tp=home native_2id=[ca-app-pub-xxx/native] inter=[ca-app-pub-xxx/inter] [InAppAd] ✓ tp=settings native_2id=✗ inter=[ca-app-pub-xxx/inter2] [InAppAd] ═══════ End registration ═══════A
✗means that slot was not registered for this touchpoint (inter-only or native-only). This is intentional and valid as long as at least one slot is present.