genuin-react-native-ads-v3 puts a Genuin IAB ad slot on screen as a single React component. You supply a tag ID; the package renders the SDK’s real ad experience and reports the full analytics funnel on its own. There is no player to wire up, no creative to fetch, and no measurement code to add.
Android only. GenuinAd renders nothing on iOS and initialize() rejects there, so shared screens stay mountable but no ad appears.
This guide is the React Native counterpart to Genuin’s Integrate the Genuin Ad SDK for Android guide.
What you get
One <GenuinAd /> gives you the SDK’s own ad UI: a shimmer placeholder while the waterfall resolves, mute and play/pause controls, a fullscreen affordance with proper activity handoff, karaoke captions on audio ads, and CTA link-out into an in-app browser.
Ad measurement is automatic. Impressions, quartiles, clicks, viewability and error reporting all flow through the SDK’s own pipeline the moment a slot renders. onAdEvent is a UI/lifecycle stream for your own logic — you do not need to subscribe to it for measurement to work.
The slot sizes itself from the format you pick, so a placement is a two-prop component in the common case, and the six IAB sizes cover standard banner through half-page.
Before you start
You need a brand API key and at least one IAB tag ID from the Genuin dashboard. The tag ID is the only per-slot input.
Step 1 — Install
npm install genuin-react-native-ads-v3
# or
yarn add genuin-react-native-ads-v3Autolinking registers the native module. Nothing to add to MainApplication, nothing to add to MainActivity, and no manifest edits.
Step 2 — Android setup
Two things in your app’s Gradle. The package brings the SDK artifacts with it at a pinned version.
Toolchain versions. React Native libraries read these from rootProject.ext, so your app decides what this module compiles with. Setting them is not optional.
// android/build.gradle
buildscript {
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
kotlinVersion = "2.0.21"
}
}Kotlin must be 2.0.21 or newer — the SDK is compiled with it and Kotlin 1.9.x cannot read 2.x metadata. compileSdk must be 36. Java/JVM target must be 17. Android Gradle Plugin 8.9.1+.
Core-library desugaring. This is the step most likely to bite you, and the build error it produces (Invoke-customs, or missing java.time) names nothing about Genuin.
// android/app/build.gradle
android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}React Native 0.83’s template already enables this with exactly this artifact, so on a current scaffold it is usually already there — check before assuming. A bare app on an older React Native, or a hand-rolled Gradle setup, may not have it.
Permissions. Nothing to add. The SDK merges in what it needs, including INTERNET and ACCESS_NETWORK_STATE. One item does need a decision from you: the ads engine brings com.google.android.gms.permission.AD_ID, and an app shipping it must declare advertising-ID use in the Play Console Data Safety form or the submission is rejected.
Step 3 — Initialize
initialize() must resolve before any slot mounts. Until it does, the native view throws IllegalStateException("SDK not initialized…"), so gate on state rather than firing and forgetting.
import { Platform } from 'react-native';
import { initialize, isInitialized } from 'genuin-react-native-ads-v3';
if (Platform.OS === 'android') {
await initialize({ apiKey: 'YOUR_BRAND_API_KEY', env: 'PROD' });
}You can check initialisation of SDK via isInitialized().
Step 4 — Render a slot
import { GenuinAd, GenuinAdFormat } from 'genuin-react-native-ads-v3';
<GenuinAd tagId="YOUR_TAG_ID" format={GenuinAdFormat.SIZE_300X250} />;tagId is the only required prop. You do not size the slot — the component applies the format’s fixed dp size itself, because the native view lays its content out at that size regardless of the box React Native gives it.
Example implementation
import { useEffect, useState } from 'react';
import { Platform } from 'react-native';
import {
initialize,
GenuinAd,
GenuinAdFormat,
type GenuinAdEvent,
} from 'genuin-react-native-ads-v3';
export default function ArticleScreen() {
const [ready, setReady] = useState(false);
useEffect(() => {
if (Platform.OS !== 'android') return;
initialize({ apiKey: 'YOUR_BRAND_API_KEY', env: 'PROD' })
.then(() => setReady(true))
.catch(console.error);
}, []);
return (
<>
<ArticleBody />
{ready && (
<GenuinAd
tagId="YOUR_TAG_ID"
format={GenuinAdFormat.SIZE_300X250}
onAdEvent={(event: GenuinAdEvent) => console.log(event)}
/>
)}
</>
);
}The repo’s example/ app renders one tag at all six sizes with a live event readout under each — run it with yarn example android.
Key configuration parameters
initialize(options)
| Option | Type | Notes |
|---|---|---|
| apiKey | string | Required. Brand API key from the dashboard. |
| env | 'PROD' | 'QA' | Required. |
| fontFamily | string | Omit for the SDK default. |
| audioBehaviorConfig.duckVolumeDuringTransientCanDuck | number | 0…1, default 0.15. Out-of-range values fall back to the default rather than crashing init. |
| audioBehaviorConfig.focusLossHandling | 'MUTE_OUTPUT' | 'PAUSE_PLAYBACK' | Default MUTE_OUTPUT. |
<GenuinAd />
| Prop | Type | Notes |
|---|---|---|
| tagId | string | Required. IAB tag ID for the placement. |
| format | GenuinAdFormat | Defaults to SIZE_300X250. Exported as a value, so format={GenuinAdFormat.SIZE_320X50} gives you autocomplete; format="SIZE_320X50" also typechecks. |
| uniqueId | string | Required only when two slots share a tagId and format on one screen. |
| onAdEvent | (event: GenuinAdEvent) => void | Lifecycle stream. Optional — analytics do not depend on it. |
| style | ViewStyle | Placement only: margins, alignSelf. Dimensions are applied for you; a width/height here overrides them. |
Multiple slots on one screen need distinct uniqueIds, because the SDK keys its per-slot state on iab_<tagId>_<format>_<uniqueId>:
Ad formats
| format | Size (dp) | Typical use |
|---|---|---|
| SIZE_320X50 | 320 × 50 | Standard banner — headers, footers |
| SIZE_320X100 | 320 x 100 | Large banner |
| SIZE_300X250 | 300 × 250 | Medium rectangle — in-article, mid-feed |
| SIZE_320X480 | 320 × 480 | Half-page portrait — interstitial-style blocks |
| SIZE_300X600 | 300 × 600 | Half-page filmstrip |
| SIZE_300X250_COLLAPSIBLE | 300 × 250, collapsing to 300 × 50 | Sticky placements that shrink after first view |
Lifecycle events
onAdEvent receives a discriminated union. Ad positions are 1-based.
| event.type | Payload | Meaning |
|---|---|---|
| loading | — | Feed began loading |
| loaded | count | Feed resolved with renderable ads |
| noAds | — | Feed resolved but empty |
| appeared | index, total, adType | Ad became active on-screen |
| started | index, total, adType | Playback began |
| completed | index, total, adType | This ad played through to the end. Only a real playout reports it — an ad that never filled reports noFill instead. Per-ad; the slot-level counterpart is allAdsCompleted. |
| changed | from, to | Pager moved between ads |
| loadTimeout | index, total, adType, timeoutMs | The waterfall budget elapsed before this ad resolved. Informational — the waterfall is not cancelled and still reports started or noFill afterwards. |
| noFill | index, total, adType | Ad waterfall produced no fill |
| allAdsCompleted | total | Every ad in the slot is done and none is pending. Slot-level rather than per-ad — the cue to collapse the slot or resume your own content. Fires once per completed pass, so a slot that resolves a fresh ad reports it again. |
| collapsed | isCollapsed | The collapsible slot was collapsed or expanded; isCollapsed is the resulting state. Never fires for the fixed sizes. |
These eleven are the complete set.
Specs and limitations
On web, importing the package throws, because the module resolves a native module at import time.
Initialize before mounting. A slot mounted before initialize() resolves throws natively.
The collapsible format needs you to resize the box. It shrinks to 50 dp natively while the React Native box stays at 250, because React Native does not re-lay-out a native child that resizes itself. Listen for the collapsed event and drive style={{ height }} from your own state — GENUIN_AD_SIZES.SIZE_300X250_COLLAPSIBLE carries both height and collapsedHeight for this.
Android
No pause() / resume(). The SDK drives playback from the Android host lifecycle, and that gate has no public API. With a JS-only router the activity stays resumed, so audio can continue after you navigate away. Unmounting the component does stop it.
Release builds work out of the box. The package applies its own ProGuard/R8 keep rules to your app automatically, verified against a minified release build with R8 full mode on — AGP 8’s default. You should not need to add anything to proguard-rules.pro.
App size. Roughly 11.9 MB added — about 7.2 MB of Genuin code and 4.7 MB of Google IMA.
Example scenarios
News or content app. A SIZE_300X250 slot placed mid-article, mounted once the article loads. One tag ID serves every article; the waterfall decides what fills it.
Scrolling feed. SIZE_320X100 inserted every N rows. Give each instance a distinct uniqueId so the SDK keeps their state apart, and reserve the row height with GENUIN_AD_SIZES so the list does not jump when the slot mounts.
Sticky footer. SIZE_320X50, or SIZE_300X250_COLLAPSIBLE where you want a larger first impression that shrinks afterwards.
Between-content interstitial. SIZE_320X480 or SIZE_300X600 on a transition screen, unmounted as soon as the user moves on — unmounting is what stops playback.
FAQs
No ads in a release build, but debug works. Look for ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType in logcat. That is R8 stripping the generic signatures Retrofit reflects on, and it surfaces as “Ad feed request returned no data” with a single-digit-millisecond latency — an empty ad response, not a client error. This package ships the keep rules that prevent it, so if you hit it, something is excluding them: check that you have not overridden proguardFiles in a way that drops consumer rules.
Every slot shows “no ads available” and logcat is silent. Check that initialize() resolved before the slot mounted, that the apiKey matches the account, that env is right, and that the tagId is non-blank and active in the dashboard.
The ad is clipped, or sits in a block of empty space. A width or height in your style is overriding the format’s own size. Drop the dimensions. The exception is the collapsible format, where the box stays at full height until you resize it from the collapsed event.
The build fails with Invoke-customs or java.time errors. Core-library desugaring is off; see step 2.
Metro logs Codegen didn't run for BGInstreamAdView. Expected and harmless in development.
Kotlin metadata errors when building. Your app’s kotlinVersion is below 2.0.21.

