|
| 1 | +# Migrating to v17 |
| 2 | + |
| 3 | +v17 keeps classic create / load / show and `<BannerAd>` / `NativeAd` **source-compatible**. |
| 4 | +Most apps need no rewrite to keep ads loading and showing. New surfaces are **opt-in**: |
| 5 | +structured errors, `ResponseInfo`, `destroy()`, fullscreen hook options, preload pools, |
| 6 | +multi-format requests, and scoped mediation adapter packages. |
| 7 | + |
| 8 | +Use this guide for the deltas publishers hit on upgrade, plus honest troubleshooting. |
| 9 | +Canonical TypeScript shapes live in the |
| 10 | +[v17 API reference](./rngma-v17-api-reference.md). |
| 11 | + |
| 12 | +## What stays the same |
| 13 | + |
| 14 | +| Area | Guidance | |
| 15 | +| ----------------------------- | ------------------------------------------------------------------------ | |
| 16 | +| Class names and call shapes | `InterstitialAd.createForAdRequest`, `load()`, `show()`, event listeners | |
| 17 | +| Banner / native components | Existing props and load paths keep working | |
| 18 | +| Legacy error `code`/`message` | Unchanged string values on existing paths | |
| 19 | +| Consent / request config | Same entry points; a few additive optional fields | |
| 20 | + |
| 21 | +You can upgrade, ship, and adopt new APIs later. |
| 22 | + |
| 23 | +## Checklist |
| 24 | + |
| 25 | +1. Upgrade `react-native-google-mobile-ads` and rebuild native apps (pods / Gradle). |
| 26 | +2. Keep using create / load / show until you need a new surface. |
| 27 | +3. Prefer branching on structured `reason` / `phase` (and hook `status`) over legacy `code` strings. |
| 28 | +4. Call `destroy()` (or let hooks / `release()` ownership rules do it) when you finish with an ad. |
| 29 | +5. Optionally adopt telemetry, pools, multi-format, or scoped adapter packages — see below. |
| 30 | + |
| 31 | +## Additive: `ResponseInfo` and paid telemetry |
| 32 | + |
| 33 | +After a successful load, read `responseInfo` on the ad (or hook / multi-format handle). |
| 34 | +Load failures — including clean **no-fill** — may carry the same record on |
| 35 | +`error.responseInfo` (or top-level on multi-format results). |
| 36 | + |
| 37 | +Paid events expose a **compact** snapshot (`PaidResponseInfo`) without the full |
| 38 | +`adapterResponses` list. Public paid field names use `currency` (not native |
| 39 | +`currencyCode`) and optional `valueMicros` as a decimal string. |
| 40 | + |
| 41 | +```ts |
| 42 | +import { AdEventType, InterstitialAd, TestIds } from 'react-native-google-mobile-ads'; |
| 43 | + |
| 44 | +const ad = InterstitialAd.createForAdRequest(TestIds.INTERSTITIAL); |
| 45 | + |
| 46 | +ad.addAdEventListener(AdEventType.LOADED, () => { |
| 47 | + console.log(ad.responseInfo?.loadedAdapterResponse?.adSourceName); |
| 48 | +}); |
| 49 | + |
| 50 | +ad.addAdEventListener(AdEventType.ERROR, error => { |
| 51 | + // Routine no-fill still has reason 'no-fill' and may include responseInfo. |
| 52 | + console.log(error.reason, error.phase, error.responseInfo?.responseId); |
| 53 | +}); |
| 54 | + |
| 55 | +ad.load(); |
| 56 | +``` |
| 57 | + |
| 58 | +Full recipes: |
| 59 | +[Revenue telemetry and auction diagnostics](/revenue-telemetry-and-auction-diagnostics). |
| 60 | + |
| 61 | +## Additive: structured errors (`reason` + `phase`) |
| 62 | + |
| 63 | +Every structured failure carries: |
| 64 | + |
| 65 | +| Field | Role | |
| 66 | +| --------------- | ---------------------------------------------------------------------- | |
| 67 | +| `code` | Legacy string (**deprecated** in v17; prefer `reason`; removed in v18) | |
| 68 | +| `message` | Human-readable text | |
| 69 | +| `reason` | Stable machine reason (`no-fill`, `network-error`, …) | |
| 70 | +| `phase` | `'load'` or `'show'` | |
| 71 | +| `responseInfo?` | Auction record when the platform attached one | |
| 72 | + |
| 73 | +There is **no** `SHOW_FAILED` event. Fail-to-show arrives as `AdEventType.ERROR` with |
| 74 | +`phase: 'show'`. On Android, presentation failures that previously emitted nothing now |
| 75 | +emit that single `ERROR` — filter on `phase` so load and show outcomes stay distinct. |
| 76 | + |
| 77 | +```ts |
| 78 | +ad.addAdEventListener(AdEventType.ERROR, error => { |
| 79 | + if (error.phase === 'show') { |
| 80 | + // Presentation failed; do not treat as a load/no-fill. |
| 81 | + return; |
| 82 | + } |
| 83 | + if (error.reason === 'no-fill' || error.reason === 'mediation-no-fill') { |
| 84 | + // Routine empty response — retry or continue without treating as a crash. |
| 85 | + return; |
| 86 | + } |
| 87 | + console.warn('load failure', error.reason, error.message); |
| 88 | +}); |
| 89 | +``` |
| 90 | + |
| 91 | +On fullscreen hooks that use the options form, use **`status`** (not `error !== null`) to |
| 92 | +tell routine `'no-fill'` from `'error'`. Details: |
| 93 | +[Revenue telemetry and auction diagnostics](/revenue-telemetry-and-auction-diagnostics) |
| 94 | +(section “Distinct no-fill vs failure”). |
| 95 | + |
| 96 | +## Additive: lifecycle — `destroy()`, ownership, impression |
| 97 | + |
| 98 | +Fullscreen and native ads expose idempotent `destroy()`: it releases JS listeners and |
| 99 | +asks native to drop the holder. Destroyed instances ignore late events and further show |
| 100 | +attempts. |
| 101 | + |
| 102 | +```ts |
| 103 | +const unsub = ad.addAdEventListener(AdEventType.CLOSED, () => { |
| 104 | + unsub(); |
| 105 | + ad.destroy(); |
| 106 | +}); |
| 107 | +``` |
| 108 | + |
| 109 | +**Ownership rules that bite in production** |
| 110 | + |
| 111 | +| Situation | Do this | |
| 112 | +| --------------------------------------------- | -------------------------------------------------------------------------- | |
| 113 | +| Imperative create / load / show | You own `destroy()` when finished | |
| 114 | +| Options-form fullscreen hook | Hook cleans up on unmount / identity change; prefer leaving it alone | |
| 115 | +| `usePooledAd` / `useMultiFormatAd` still owns | **Do not** call `destroy()` — call `release()` first if you need ownership | |
| 116 | +| After `release()` | You own `destroy()` and any staleness check | |
| 117 | +| Pool `poll()` filled result | Ownership transferred; destroy the held ad when done | |
| 118 | + |
| 119 | +`AdEventType.IMPRESSION` is additive (no payload). Hooks also expose an `impression` |
| 120 | +boolean on the options-form result. |
| 121 | + |
| 122 | +Reload after `CLOSED` or fail-to-show reuses the same request id once native holders |
| 123 | +auto-evict — you do not need a new class instance solely because the previous show ended. |
| 124 | + |
| 125 | +## Fullscreen hooks: positional form deprecated |
| 126 | + |
| 127 | +Passing an options object opts into the v17 result (`status`, `autoLoad`, structured |
| 128 | +`error`). The positional form still works, is **deprecated in v17**, and is **removed in |
| 129 | +v18**. |
| 130 | + |
| 131 | +```tsx |
| 132 | +// Before (deprecated) |
| 133 | +const { isLoaded, load, show, error } = useInterstitialAd(unit); |
| 134 | +useEffect(() => { |
| 135 | + if (consentReady) load(); |
| 136 | +}, [consentReady, load]); |
| 137 | + |
| 138 | +// After |
| 139 | +const { status, show, error } = useInterstitialAd({ |
| 140 | + adUnitId: unit, |
| 141 | + autoLoad: consentReady, |
| 142 | +}); |
| 143 | +``` |
| 144 | + |
| 145 | +| Positional | Options form | |
| 146 | +| --------------------------- | ------------------------------------------------- | |
| 147 | +| `isLoaded` | `status === 'loaded'` | |
| 148 | +| `isShowing` | `status === 'showing'` | |
| 149 | +| `isClosed` | `status === 'closed'` | |
| 150 | +| `error?: Error` | `error: AdError \| null` + `'no-fill'` status arm | |
| 151 | +| `load` / `show` / `destroy` | Same, plus `retry` | |
| 152 | + |
| 153 | +`autoLoad` is a load **policy**, not a second placement identity. Setting it `false` |
| 154 | +stops future automatic loads; it does not cancel an in-flight request (platforms do not |
| 155 | +expose load cancellation). See [Displaying Ads via Hook](/displaying-ads-hook). |
| 156 | + |
| 157 | +Automatic loading does **not** re-fire after `'closed'`. Call `load()` / `retry()` (or |
| 158 | +flip `autoLoad` with a new consent-ready value) when you want the next ad. |
| 159 | + |
| 160 | +## Optional: pools and multi-format |
| 161 | + |
| 162 | +Warm inventory and native-or-banner competitive requests are opt-in. Prefer presets — |
| 163 | +do not hand-roll capability matrices. |
| 164 | + |
| 165 | +| Need | Guide | |
| 166 | +| ----------------------------------- | -------------------------------------------------------------------------------- | |
| 167 | +| Fullscreen pool + poll at show time | [Preload pools and multi-format recipes](/preload-pools-and-multiformat-recipes) | |
| 168 | +| Count-1 native **or** GAM banner | Same guide · `MultiFormatAdPresets.nativeOrBanner` | |
| 169 | +| Types, expiry, ownership | [v17 API reference](./rngma-v17-api-reference.md#ad-pools-new) | |
| 170 | + |
| 171 | +## Optional: scoped mediation adapter packages |
| 172 | + |
| 173 | +Third-party network adapters ship as separate packages under |
| 174 | +`@react-native-google-mobile-ads/<network>` (for example `applovin`, `facebook`, |
| 175 | +`unity`). Install only the networks you mediate; they are **not** required for AdMob-only |
| 176 | +apps. Follow [Mediation](/mediation) for AdMob UI setup and native wiring. |
| 177 | + |
| 178 | +## Troubleshooting |
| 179 | + |
| 180 | +### Ads still load, but analytics / retries look wrong |
| 181 | + |
| 182 | +| Symptom | Likely cause / fix | |
| 183 | +| ---------------------------------------------- | ----------------------------------------------------------------------------- | |
| 184 | +| Treating every `error !== null` as a hard fail | Options-form hooks populate `error` on `'no-fill'` too — branch on `status` | |
| 185 | +| Looking for `SHOW_FAILED` | Does not exist — use `ERROR` with `phase: 'show'` | |
| 186 | +| Android never saw show failures before | Expected — Android now emits one `ERROR` (`phase: 'show'`) on fail-to-present | |
| 187 | +| Paid payload missing `currencyCode` | Public field is `currency` | |
| 188 | +| Full waterfall missing on every paid event | By design — use load-time `ResponseInfo` for waterfalls; paid is compact | |
| 189 | + |
| 190 | +### Destroy / ownership surprises |
| 191 | + |
| 192 | +| Symptom | Likely cause / fix | |
| 193 | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | |
| 194 | +| Hook still reports filled after you called `destroy()` | Destroyed hook-owned inventory — `release()` first, or let the hook destroy | |
| 195 | +| Post-show events never fire after pooled show | Hook-owned show destroys on show-promise settle — `release()` before show if you need `CLOSED` / reward | |
| 196 | +| Late `LOADED` after a newer `load()` / `destroy()` | Stale generation — ignored by design; do not treat as a second fill | |
| 197 | + |
| 198 | +### Hooks and pools |
| 199 | + |
| 200 | +| Symptom | Likely cause / fix | |
| 201 | +| ----------------------------------------------------- | ---------------------------------------------------------------------------- | |
| 202 | +| Options-form hook stuck on `'idle'` | `autoLoad` is false — confirm echoed `autoLoad` on the result | |
| 203 | +| Next ad never warms after dismissal | Automatic load does not re-fire after `'closed'` — call `load()` / `retry()` | |
| 204 | +| `useAdPool` / `usePooledAd` stuck on `absent` / empty | `poolId` typo or missing `AdPoolProvider` entry | |
| 205 | +| Two screens starve a depth-1 pool | Two `usePooledAd(sameId)` owners competing | |
| 206 | +| Positional `useInterstitialAd(unit)` struck through | Deprecated — pass an options object | |
| 207 | + |
| 208 | +### Fill and setup (unchanged) |
| 209 | + |
| 210 | +Account approval, new units, test devices, and `applicationId` issues still dominate |
| 211 | +“no ads” reports. See |
| 212 | +[Common reasons for ads not showing](/common-reasons-for-ads-not-showing) and |
| 213 | +[Ad Inspector](/ad-inspector). |
| 214 | + |
| 215 | +### Diagnostics dump for issue reports |
| 216 | + |
| 217 | +| Kind of bug | Include | |
| 218 | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | |
| 219 | +| Classic load / show / fill | Ad Inspector outcome, ad unit, test device, platform SDK version | |
| 220 | +| Auction / mediation mix | Summarized load-time `ResponseInfo` ([Revenue telemetry](/revenue-telemetry-and-auction-diagnostics)) | |
| 221 | +| Pools / multi-format / preload | `JSON.stringify(getAdCapabilities(), null, 2)` ([Preload pools diagnostics](/preload-pools-and-multiformat-recipes#diagnostics-dump)) | |
| 222 | +| Structured error handling | `reason`, `phase`, `code`, `message`, and whether `responseInfo` was present | |
| 223 | + |
| 224 | +## What this guide does not cover |
| 225 | + |
| 226 | +- Removing shims or dropping deprecated positional hooks (**v18**) |
| 227 | +- Multi-count requests (`numberOfAds` / `requestCount` > 1) |
| 228 | +- Mediation **host** SDKs (MAX, CloudX, …) — out of this package surface |
| 229 | +- Invented eCPM / lift helpers — not part of the public API |
| 230 | + |
| 231 | +## Related guides |
| 232 | + |
| 233 | +- [Revenue telemetry and auction diagnostics](/revenue-telemetry-and-auction-diagnostics) |
| 234 | +- [Preload pools and multi-format recipes](/preload-pools-and-multiformat-recipes) |
| 235 | +- [Displaying Ads](/displaying-ads) |
| 236 | +- [Displaying Ads via Hook](/displaying-ads-hook) |
| 237 | +- [Mediation](/mediation) |
| 238 | +- [Common reasons for ads not showing](/common-reasons-for-ads-not-showing) |
| 239 | +- [Migrating to v15](/migrating-to-v15) — prior Android Kotlin floor note |
| 240 | +- [v17 API reference](./rngma-v17-api-reference.md) — types, migration sketches, first failure modes |
0 commit comments