View as Markdown

React Native

The Ezoic React Native SDK lets you display banner, native, outstream video, instream video, rewarded, and interstitial ads in React Native apps with Prebid header bidding, Google Ad Manager, remote configuration, consent handling, and pageview tracking, all from a single JavaScript API.

The package is a thin bridge over Ezoic's native Android and iOS ad stacks, so ad loading and auctions run natively while you write only React Native code. This guide is self-contained: it covers everything you need to ship on both platforms.

Requirements

  • React Native 0.76 or higher, with the New Architecture enabled
  • Node.js 18 or higher
  • For Android apps: Android SDK 24 or higher, Android Gradle Plugin 8.0 or higher, Kotlin 2.1 or higher
  • For iOS apps: iOS 15.0 or higher, Xcode 26.2 or higher, CocoaPods 1.12 or higher
  • A Google Mobile Ads application ID provided by Ezoic
  • A domain configured for your site in Ezoic

Authentication is handled by the app's bundle/package identifier plus the configured domain. There is no client-side API key.

Installation

Install the package:

npm install @ezoic/react-native-sdk@1.19.1

The package ships the native Android and iOS Ezoic SDKs as transitive dependencies and registers them through React Native autolinking, so you do not add the native ad SDKs yourself.

The current React Native SDK version is 1.19.1, which uses native SDK 1.19.1 (Google Mobile Ads 25.2.0 on Android, 13 on iOS).

Upgrading to 1.19.1: no app changes are needed. Android release builds made with Android Gradle Plugin 9 no longer crash at startup with "Failed to create an instance of androidx.work.impl.WorkDatabase". On iOS, consent set with setGDPRConsent or setGPPConsent now reaches Google Mobile Ads, IMA and Amazon.

Upgrading to 1.19.0: no app changes are needed.

  • Banners in a FlatList, ScrollView with removeClippedSubviews, or a tab navigator on Android no longer go blank after they scroll out of view and back.
  • EzoicAds.initialize no longer fails with "already initialized" (1002) after a Metro, CodePush or Expo reload. Concurrent calls share one initialization, and a failed call can be retried.
  • destroy() on a rewarded or interstitial ad that was never shown now releases it, so the same ad unit can load again.
  • Ad requests no longer wait 2 seconds for a pageview at launch.
  • setSubjectToCOPPA(true) now also reaches Google Ad Manager, instream video tags and Amazon, and the device ID is kept only while storage consent allows it.

Upgrading to 1.18.0: no app changes are needed. Native ads are now drawn by the template built into the Ezoic Android and iOS SDKs, so they look the same in every Ezoic SDK and now show an "Ad" badge next to the advertiser name. It also fixes EzoicAds.initialize never resolving on Android in 1.17.0, which stopped all ads from loading there. You can restyle them with the new adStyle prop; see Styling native ads. Rewarded ads also report an outcome for every load.

Upgrading to 1.17.0: no app changes are needed. EzoicNativeAdView now hands its rendered ad view to the native SDK for you, so native ads are reported as viewed (at least half on screen for one continuous second). Ad measurement now matches Ezoic's web reporting more closely: viewed impressions for every format, an outcome for every ad opportunity including no-fills, and revenue and floor values reported the same way as on the web. Full-screen ads and returning from the background no longer count as new pageviews. If you label your launch screen, see Labeling screens.

Upgrading to 1.16.0: no app changes are needed. Banners can now refresh when Ezoic enables it for the ad unit, and you can control it with the new refreshIntervalSeconds / refreshMaxCount options (see Banner refresh).

Upgrade to 1.13.3 or later if Android banners ignore the size prop (1.13.2 and earlier always requested the adaptive 320x50 on phones) or iOS banners occasionally mount without firing onLoad or onError. React Native SDK 1.11.1 and earlier pin native SDK 1.11.x and have no consent dialog or screen labels; upgrade to 1.13.0. See Privacy and Consent for what changes when you upgrade.

iOS pods

The native EzoicAdsSDK ships as a binary Swift framework that depends on PrebidMobile (a Swift source pod). Consuming a binary Swift framework with Swift dependencies requires framework-based linkage, so your app's Podfile must enable static frameworks:

use_frameworks! :linkage => :static

Then install pods:

cd ios && RCT_NEW_ARCH_ENABLED=1 pod install

This resolves the native EzoicAdsSDK framework along with its Prebid Mobile and Google Mobile Ads dependencies.

Android Setup

The package's Gradle module declares the Ezoic Android SDK dependency automatically. Your app project must be able to resolve it, which standard React Native apps already do because they include Google's Maven repository and Maven Central.

If your android/build.gradle or android/settings.gradle customizes repositories, confirm both are present:

repositories {
    google()
    mavenCentral()
}

Google Mobile Ads application ID

Add your Google Mobile Ads application ID to android/app/src/main/AndroidManifest.xml. This ID is provided by Ezoic and can be found in your Ezoic dashboard. Use the value assigned to your app unless your Ezoic representative gives you a different one.

<manifest>
    <application>
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX" />
    </application>
</manifest>

Advertising ID permission

If your app targets Android 12 (API 31) or higher and uses the advertising ID, add the permission to AndroidManifest.xml:

<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

iOS Setup

Google Mobile Ads application ID

Add your Google Mobile Ads application ID to ios/<YourApp>/Info.plist. This ID is provided by Ezoic and can be found in your Ezoic dashboard. Use the value assigned to your app unless your Ezoic representative gives you a different one.

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX</string>
Also set GADIsAdManagerApp to true in Info.plist so Google Mobile Ads runs in Ad Manager mode.

App Tracking Transparency

ATT has the single biggest impact on fill and CPM. Without it the IDFA is unavailable and much of programmatic demand will not bid or bids far lower.

Add the tracking usage description to ios/<YourApp>/Info.plist. Apple requires this string in the app's Info.plist (it appears in the App Store privacy label and system prompt) — it cannot come from the SDK.

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

The SDK presents the ATT prompt for you on iOS. When you call EzoicAds.initialize, the native iOS SDK shows the prompt (if undetermined) and waits for the decision before starting the ad stack, so the IDFA (if granted) is attached to the first ad request. You only need to add the NSUserTrackingUsageDescription string above.

import { EzoicAds } from '@ezoic/react-native-sdk';

// The SDK requests ATT (if undetermined) before loading ads on iOS.
await EzoicAds.initialize({ domain: 'example.com' });

To drive ATT yourself instead (for a pre-prompt or custom timing), pass requestATTBeforeAds: false and request authorization before initializing — for example with react-native-tracking-transparency:

import { requestTrackingPermission } from 'react-native-tracking-transparency';
import { EzoicAds } from '@ezoic/react-native-sdk';

await requestTrackingPermission();
await EzoicAds.initialize({ domain: 'example.com', requestATTBeforeAds: false });

SKAdNetwork

SKAdNetworkItems lets buyers attribute installs when the IDFA is unavailable; missing identifiers suppress demand. Apple reads this key only from the app's main Info.plist — it is not aggregated from frameworks or SDKs.

Add the SKAdNetworkItems array with Google's published identifiers (cstr6suwn9.skadnetwork plus participating third-party buyers). Copy the current, complete list from Google's Prepare privacy strategies page; identifiers must be lowercase.

<key>SKAdNetworkItems</key>
<array>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>cstr6suwn9.skadnetwork</string>
    </dict>
    <!-- … plus the remaining identifiers from Google's list … -->
</array>

app-ads.txt

For both platforms, host an app-ads.txt file at the root of the developer website listed on your app's store page (for example, https://example.com/app-ads.txt). It authorizes the buyers that may sell your inventory; a missing or incomplete file causes most programmatic demand to be filtered out. Ezoic provides the required entries — confirm the file is published and current.

Initialize the SDK

Initialize Ezoic once, early in your app lifecycle, before rendering any ads.

import { EzoicAds } from '@ezoic/react-native-sdk';
import { useEffect } from 'react';

export function App() {
    useEffect(() => {
        EzoicAds.initialize({
            domain: 'example.com',
            debugEnabled: false,
            testMode: false
        });
    }, []);

    return null;
}

domain must match the domain configured for your site in Ezoic. Set debugEnabled: true during development to surface verbose native logs in Logcat (Android) and the Xcode console (iOS). Use testMode: true only while integrating; remove it for production traffic.

initialize resolves once the native SDK has finished bootstrapping. You can await it before rendering ads:

await EzoicAds.initialize({ domain: 'example.com' });

initialize accepts these fields:

Field Default Description
domain (required) Your Ezoic domain
autoReadConsent true Read IABTCF_* / IABGPP_* consent keys written by a CMP
subjectToCOPPA false Treat the user as subject to COPPA
requestATTBeforeAds true iOS only: request App Tracking Transparency before the first ad
debugEnabled false Verbose native logging
testMode false Ezoic $0.00 test ads on debug builds and simulators. Disable before release
autoTrackPageviews true Record a pageview automatically on native screen changes. See Pageview Tracking
cmpEnabled true Enable the built-in TCF CMP. Set false if you run your own CMP. See Privacy and Consent
autoPresentConsent true Present the consent dialog (if required) right after initialize resolves

Add a Banner Ad

Render EzoicBannerView where the banner should appear. Give it a width (and an optional height for the filled creative) through style. While the view is collapsed, the component sets height to 0 so an unfilled slot does not leave a blank gap.

import { EzoicBannerView } from '@ezoic/react-native-sdk';
import { SafeAreaView } from 'react-native';

export function ArticleScreen() {
    return (
        <SafeAreaView>
            <EzoicBannerView
                adUnitIdentifier="12345"
                size="300x250"
                collapseOnNoFill={true}
                style={{ width: 300, height: 250 }}
                onLoad={() => console.log('Ezoic banner loaded')}
                onError={(error) => console.warn('Ezoic banner failed', error)}
                onSizeChange={({ width, height }) =>
                    console.log('Ezoic banner size', width, height)
                }
            />
        </SafeAreaView>
    );
}

Replace 12345 with your Ezoic ad unit identifier. The native SDKs fetch the Google Ad Manager ad unit, Prebid configuration, targeting values, supported sizes, and refresh interval from Ezoic servers, so you do not configure those in the app.

When no ad fills, the banner collapses (collapseOnNoFill, default true) and the component overrides style height to 0. If a refresh does not fill, the previous ad stays visible. onSizeChange reports the displayed size, or { width: 0, height: 0 } when collapsed.

The native Ezoic ad unit identifier is numeric. Pass it as a string in React Native, for example "12345".

Banner refresh is off unless Ezoic's servers enable it for the ad unit. To control it yourself, pass refreshIntervalSeconds (0 = off, minimum 30) and/or refreshMaxCount (refreshes per load, 0 = no limit, maximum 100); leave them out to follow Ezoic's setting. Both are read when the banner loads.

<EzoicBannerView adUnitIdentifier="12345" refreshIntervalSeconds={60} refreshMaxCount={10} />

The countdown only runs while at least half of the banner is on screen and visible and your app is in the foreground; it pauses otherwise. If your Google Ad Manager ad unit has its own auto-refresh turned on, turn it off so the banner is not refreshed twice.

Pass size as a widthxheight string. Common sizes are:

  • "320x50": Banner
  • "320x100": Large Banner
  • "300x250": Medium Rectangle
  • "468x60": Full Banner
  • "728x90": Leaderboard

You can also pass a comma-separated list to let the auction choose among several sizes, for example size="300x250,320x50". Always size the style so the view can hold the largest size you request.

The banner loads once the component mounts with all its props applied. Changing adUnitIdentifier or size afterwards destroys the current banner and loads a new one for the new values; collapseOnNoFill changes apply to the next load outcome without a reload. Keep the size string stable across renders so re-renders do not trigger reloads.

EzoicBannerView supports these event props:

  • onLoad: the banner received an ad
  • onError: the banner failed to load; receives an error object
  • onSizeChange: the displayed size changed; receives { width, height }
  • onImpression: an impression was recorded
  • onClick: the user tapped the ad
  • onOpen: the ad opened a full-screen overlay
  • onClose: the full-screen overlay was dismissed

Add a Native Ad

Native ads deliver ad assets (headline, icon, advertiser, media, body text, and call to action) that are laid out as a card in your content, rather than in a fixed banner or full-screen format. Like EzoicBannerView, EzoicNativeAdView is a React component: render it where the ad should appear, sized through style.

import { EzoicNativeAdView } from '@ezoic/react-native-sdk';

export function ArticleScreen() {
    return (
        <EzoicNativeAdView
            adUnitIdentifier="12345"
            style={{ width: '100%', height: 320 }}
            onLoad={() => console.log('Ezoic native ad loaded')}
            onError={(error) => console.warn('Ezoic native ad failed', error)}
            onImpression={() => console.log('Ezoic native ad impression')}
            onClick={() => console.log('Ezoic native ad clicked')}
            onOpen={() => console.log('Ezoic native ad opened an overlay')}
            onClose={() => console.log('Ezoic native ad overlay closed')}
        />
    );
}

Replace 12345 with your Ezoic ad unit identifier. The package renders the ad in Ezoic's built-in template, a Google Mobile Ads NativeAdView that lays out the headline, icon, advertiser, media, body, and call to action for you (see Styling native ads); unlike EzoicBannerView, EzoicNativeAdView has no size prop, so the template lays out its assets inside the bounds you give it through style. From version 1.17.0 the package registers the rendered ad view with the native SDK, so the ad is reported as viewed once at least half of it has been on screen for one continuous second; there is nothing to call.

EzoicNativeAdView supports these event props:

  • onLoad: the native ad received an ad
  • onError: the native ad failed to load; receives an error object
  • onImpression: an impression was recorded
  • onClick: the user tapped the ad
  • onOpen: the ad opened a full-screen overlay
  • onClose: the full-screen overlay was dismissed
Unlike EzoicBannerView, EzoicNativeAdView accepts the Ezoic ad unit identifier as either a string or a number, for example "12345" or 12345.

Styling native ads

Every native ad is drawn in the same template that the Ezoic Android and iOS SDKs provide (EzoicNativeAdTemplateView), so it looks the same in React Native as in a native app. From version 1.18.0, pass adStyle to restyle it to match your app:

<EzoicNativeAdView
    adUnitIdentifier="12345"
    style={{ width: '100%', height: 360 }}
    adStyle={{
        backgroundColor: '#1E1E2E',
        cornerRadius: 16,
        headlineTextColor: '#FFFFFF',
        bodyTextColor: '#CDD6F4',
        callToActionBackgroundColor: '#89B4FA',
        callToActionTextColor: '#1E1E2E',
        callToActionCornerRadius: 22,
        adLabelText: 'Sponsored',
    }}
/>

Every key is optional; leave one out to keep the default. Colors accept React Native color strings such as '#1E1E2E', 'white' or 'rgba(0, 0, 0, 0.5)'; PlatformColor values are ignored. Sizes are in density-independent pixels (points on iOS). Changing adStyle redraws the ad in place without reloading it.

Key Default Notes
backgroundColor none (transparent) Card background
cornerRadius 0 Card corners; content is clipped to them
padding 8 Inner padding
headlineTextColor, headlineTextSize theme, 16 The headline is bold
advertiserTextColor, advertiserTextSize theme, 12
bodyTextColor, bodyTextSize theme, 14
callToActionBackgroundColor, callToActionTextColor platform button
callToActionTextSize, callToActionCornerRadius 14, 4 The radius applies when a background color is set
mediaHeight 175 At least 120, which Google requires for native media
adLabelText "Ad" The ad attribution badge; a blank value falls back to "Ad"
adLabelTextColor, adLabelBackgroundColor #202124, #FBBC04

From top to bottom, the template shows a header row with the icon, the bold headline and, under it, the "Ad" badge and the advertiser name; then the media (image or video), the body text and a full-width call-to-action button. It only shows the assets an ad includes, so a card may have no icon or advertiser line. Fonts are the system defaults, and colors you leave out follow the platform's light and dark theme. Give the view enough height through style: about 330 with the defaults, more with extra padding, larger text or a two-line headline.

If the ad needs a different layout altogether, render it with the native SDKs instead, from your own native UI component. The Ezoic Android and iOS SDKs return the ad's individual assets for you to lay out however you like; see Build your own layout on Android and on iOS. Both native SDKs are already installed as dependencies of this package.

Add a Rewarded Ad

Rewarded ads are full-screen ads that grant an in-app reward when the user finishes watching. Unlike banners, they are not React components: load one imperatively ahead of time (for example, at the start of a level), then present it at a natural break. show() resolves with the earned reward, or null if the user dismissed the ad early.

import { EzoicRewardedAd } from '@ezoic/react-native-sdk';

async function runRewardedAd() {
    try {
        const ad = await EzoicRewardedAd.load('12345');

        // Optional: observe lifecycle events
        ad.setListeners({
            onDismissed: () => console.log('Rewarded ad closed'),
            onFailedToShow: (error) => console.warn('Show failed', error.message),
        });

        const reward = await ad.show();
        if (reward) {
            console.log(`Earned ${reward.amount} ${reward.type}`);
            grantReward(reward.amount);
        }

        // Rewarded ads are single-use — release the handle when done.
        ad.destroy();
    } catch (error) {
        console.warn('Rewarded ad failed to load', error);
    }
}

Replace 12345 with your Ezoic ad unit identifier (passed as a string). Load and show are separate steps; calling show() before the ad has loaded rejects with an error. Load a new EzoicRewardedAd for each reward opportunity.

You can pass a reward name when you show the ad. The name appears in your reports.

const reward = await ad.show('extra life');

The reward type and amount come from the reward configured on the Google Ad Manager rewarded ad unit. setListeners accepts onShown, onFailedToShow, onImpression, onClicked, onUserEarnedReward, and onDismissed — all optional.

Add an Interstitial Ad

Interstitial ads are full-screen ads shown at natural transition points (for example, between levels or screens). Unlike rewarded ads, they grant no reward. Like rewarded ads, they are not React components: load one imperatively ahead of time, then present it at a natural break. show() resolves when the ad is dismissed, or rejects if it fails to present.

import { EzoicInterstitialAd } from '@ezoic/react-native-sdk';

async function runInterstitialAd() {
    try {
        const ad = await EzoicInterstitialAd.load('12345');

        // Optional: observe lifecycle events
        ad.setListeners({
            onShown: () => console.log('Interstitial ad shown'),
            onFailedToShow: (error) => console.warn('Show failed', error.message),
        });

        await ad.show();
        console.log('Interstitial ad closed');
    } catch (error) {
        console.warn('Interstitial ad failed to load', error);
    }
}

Replace 12345 with your Ezoic ad unit identifier (passed as a string). Load and show are separate steps; calling show() before the ad has loaded rejects with an error. Interstitial ads are single-use and auto-destroy once dismissed — load a new EzoicInterstitialAd for each opportunity. Call ad.destroy() yourself only if you loaded an ad and never showed it.

setListeners accepts onShown, onFailedToShow, onImpression, onClicked, and onDismissed — all optional.

Add an Outstream Video Ad

Render EzoicOutstreamAdView where the video should appear. Like EzoicNativeAdView, it has no size prop — size it with style and the native view lays the player out inside those bounds. It is view-managed: mounting the component loads the ad, unmounting destroys it.

import { EzoicOutstreamAdView } from '@ezoic/react-native-sdk';

export function ArticleScreen() {
    return (
        <EzoicOutstreamAdView
            adUnitIdentifier="12345"
            style={{ width: '100%', height: 250 }}
            onLoad={() => console.log('Ezoic outstream ad loaded')}
            onError={(error) => console.warn('Ezoic outstream ad failed', error)}
            onImpression={() => console.log('Ezoic outstream ad impression')}
            onClick={() => console.log('Ezoic outstream ad clicked')}
            onOpen={() => console.log('Ezoic outstream ad opened an overlay')}
            onClose={() => console.log('Ezoic outstream ad overlay closed')}
            onSizeChange={({ width, height }) =>
                console.log('Ezoic outstream size', width, height)
            }
        />
    );
}

Replace 12345 with your Ezoic ad unit identifier. The native SDKs render the ad inline through Google Ad Manager at the size configured on the server.

EzoicOutstreamAdView supports these event props:

  • onLoad: the outstream ad received an ad
  • onError: the outstream ad failed to load; receives an error object
  • onSizeChange: the displayed size changed; receives { width, height }
  • onImpression: an impression was recorded
  • onClick: the user tapped the ad
  • onOpen: the ad opened a full-screen overlay
  • onClose: the full-screen overlay was dismissed

EzoicOutstreamAdView also accepts collapseOnNoFill (default true) and collapses on no-fill the same way as EzoicBannerView.

Like EzoicNativeAdView, EzoicOutstreamAdView accepts the Ezoic ad unit identifier as either a string or a number, for example "12345" or 12345.

Add an Instream Video Ad

EzoicInstreamAd is a view-less controller for instream (pre/mid/post-roll) video. Unlike the banner, native, and outstream components, it renders nothing: your app owns the video player and the Google IMA SDK, and its sole deliverable is a Google Ad Manager VAST ad-tag URL string you feed to your own IMA AdsRequest. Unlike rewarded and interstitial ads, a controller is multi-use — it is not auto-destroyed, so you load() it repeatedly and destroy() it yourself.

import { EzoicInstreamAd } from '@ezoic/react-native-sdk';

async function runInstreamAd() {
    const instream = new EzoicInstreamAd('12345');

    try {
        const adTagUrl = await instream.load({ contentUrl: playingVideoUrl });
        adsLoader.requestAds({ adTagUrl });

        // On an IMA ad error, walk down the floor waterfall to the next tag.
        const next = await instream.getNextAdTagUrl(); // null once exhausted
        if (next) adsLoader.requestAds({ adTagUrl: next });

        // On the IMA STARTED event, fire the Ezoic impression pixel.
        await instream.reportImpression({ revenueUsd: 0.42 });
    } catch (error) {
        console.warn('Instream ad failed to load', error);
    } finally {
        await instream.destroy();
    }
}

Replace 12345 with your Ezoic ad unit identifier (passed as a string). load({ contentUrl }) resolves with the tag URL, or rejects on no fill, an uninitialized SDK, or an overlapping load already in flight for this id; contentUrl is optional and, when supplied, is added to the tag for contextual targeting. getNextAdTagUrl() resolves to null once the waterfall is exhausted. reportImpression({ revenueUsd }) records the Ezoic impression on the IMA STARTED event; revenueUsd is optional. Call destroy() when the ad unit is no longer needed — the controller otherwise stays alive and reusable across loads.

The native SDKs include an IAB TCF 2.4 consent management platform (CMP ID 299). It only acts for users in GDPR regions; elsewhere no dialog is shown and ads load as before.

The dialog is used only when both of these are true:

  • Ezoic has enabled the consent dialog for your domain. This is a server-side setting.
  • cmpEnabled is true in the initialize config. This is the default.
If your app already runs another CMP (for example UMP or OneTrust), you must set cmpEnabled: false. See Using your own CMP.

The dialog is presented for you. Once initialize resolves, the SDK calls presentConsentIfRequired() once on your behalf (autoPresentConsent: true, the default). Outside GDPR regions, with cmpEnabled: false, when another CMP is present, when you called setGDPRConsent before initialize, or with autoReadConsent: false, nothing is shown.

If initialize resolves before any screen is showing (no foreground Activity on Android, no presented view controller on iOS, for example during a very early cold start), the automatic presentation is skipped. Call presentConsentIfRequired() from your first screen in that case.

You must also add a "Privacy settings" button or menu item that is always reachable and calls presentConsentSettings(). TCF policy requires users to be able to reopen the dialog and change or withdraw consent at any time.

<Button
  title="Privacy settings"
  onPress={() => EzoicAds.presentConsentSettings()}
/>

presentConsentSettings() reopens the dialog with the user's stored choices in GDPR regions and resolves notRequired elsewhere, with cmpEnabled: false, or when another CMP is present.

Upgrading from 1.11.x: cmpEnabled and autoPresentConsent default to true, so GDPR-region users now see the built-in dialog after initialize and ad loads wait for their decision. If you pass consent yourself, move setGDPRConsent before initialize and set cmpEnabled: false. See Setting consent manually.

Choosing when the dialog appears

To control the timing or read the outcome, turn automatic presentation off and call presentConsentIfRequired() yourself, for example from your first screen:

import { EzoicAds } from '@ezoic/react-native-sdk';

await EzoicAds.initialize({ domain: 'example.com', autoPresentConsent: false });

const outcome = await EzoicAds.presentConsentIfRequired();
switch (outcome.type) {
  case 'decided':
    console.log('User chose', outcome.decision); // 'acceptAll' | 'rejectAll' | 'custom'
    break;
  case 'failed':
    console.log('Consent UI failed', outcome.code, outcome.message);
    break;
  default:
    break; // 'notRequired' | 'alreadyDecided' | 'dismissed' | 'alreadyPresenting'
}

presentConsentIfRequired() can be called at any time, and repeat calls are harmless: you get alreadyPresenting while a dialog is in flight and alreadyDecided once a valid decision is stored. Called before initialization finishes, it waits for the init response. Present again whenever isConsentRequired() is true and no decision has been made (for example after dismissed or failed).

presentConsentIfRequired() and presentConsentSettings() always resolve (never reject) with an EzoicConsentOutcome:

type When
notRequired GDPR doesn't apply, the built-in CMP is disabled, another CMP owns consent, or consent is managed by the app (setGDPRConsent, or autoReadConsent: false)
alreadyDecided A still-valid decision is stored; no dialog shown
decided The user chose decision (acceptAll, rejectAll or custom); the choice is saved
dismissed The dialog closed without a choice; ads stay gated for this session
alreadyPresenting A consent dialog is already on screen or being prepared
failed The dialog couldn't be shown. code and message come from the native error. code: -1 with message: 'No foreground Activity' (on both platforms) means there was no foreground Activity or view controller: the native SDK was not called and ads stay gated, so call presentConsentIfRequired() again once a screen is showing

Other consent calls:

  • isConsentRequired() resolves true whenever GDPR applies and the built-in CMP is in charge (including after the user has decided), false otherwise, and null until the init request completes or when the server sent no consent information.
  • resetConsent() deletes the stored decision so the dialog shows again. Ads are gated again until the user decides.

In GDPR regions, while the built-in CMP is in charge and no decision is stored, ad loads wait for the user's decision:

  • While the consent dialog is loading or on screen, ad loads wait up to 5 minutes in total per dialog.
  • While no dialog is in progress, the dialog is covered, or the app is in the background, ad loads wait up to 10 seconds.

If the wait runs out, the ad load fails with error code 5001 (EzoicErrorCode.consentRequired). If the native SDK can't show the dialog at all (for example a network error), it returns failed with the native error code and ads proceed without a TC string (limited ads). A failed outcome with code: -1 is different: ads stay gated, wait up to 10 seconds, and then fail with 5001.

Handling error 5001

Consent is checked when an ad loads, so 5001 arrives as:

  • code on the ad views' onError (banner, native, outstream).
  • error.userInfo.code on rejected rewarded, interstitial, and instream load() promises. The rejection's own code stays the string 'EzoicAds', and its message is the native one ("User consent is required to load ads.").
import {
  EzoicAds,
  EzoicBannerView,
  EzoicErrorCode,
  EzoicRewardedAd,
} from '@ezoic/react-native-sdk';

<EzoicBannerView
  adUnitIdentifier="123456"
  onError={(e) => {
    if (e.code === EzoicErrorCode.consentRequired) {
      // The user hasn't decided yet; e.g. offer EzoicAds.presentConsentIfRequired().
    }
  }}
/>;

try {
  const ad = await EzoicRewardedAd.load('123456');
  await ad.show();
} catch (e: any) {
  if (e?.userInfo?.code === EzoicErrorCode.consentRequired) {
    await EzoicAds.presentConsentIfRequired();
  }
}

Using your own CMP

Set cmpEnabled: false. The SDK then reads your CMP's IABTCF_* (TCF) and IABGPP_* (GPP) keys as before. The built-in CMP also stays out of the way automatically if it finds IABTCF_CmpSdkID set to another CMP's ID.

await EzoicAds.initialize({ domain: 'example.com', cmpEnabled: false });

Whichever CMP writes them, the native SDKs automatically read consent signals from platform storage — UserDefaults on iOS and SharedPreferences on Android:

  • TCF v2 consent from IABTCF_* keys
  • GPP consent from IABGPP_* keys
  • US Privacy consent from the standard IAB US Privacy key

setGDPRConsent and the built-in CMP are mutually exclusive. The value lasts for the current process only, so call setGDPRConsent before initialize on every launch, or set cmpEnabled: false. Otherwise each cold start begins with the built-in CMP in charge until your call lands, and it can gate ad loads, show its dialog, and write IABTCF_* keys. The SDK doesn't write your consent string to IABTCF_* keys for other SDKs, so your own CMP must do that.

EzoicAds.setGDPRConsent(true, '<IAB TCF consent string>');
EzoicAds.setGPPConsent('GPP_STRING', '7');
EzoicAds.setSubjectToCOPPA(false);
await EzoicAds.initialize({ domain: 'example.com', cmpEnabled: false });
  • setGDPRConsent(applies, consentString): whether GDPR applies and the TCF consent string
  • setGPPConsent(gppString, sectionIds): the GPP string and applicable section IDs
  • setSubjectToCOPPA(value): whether the user is subject to COPPA

Pageview Tracking

Ezoic reports app traffic per screen, the way it reports a website per URL. Apps have no URLs, so the SDK gives each screen one:

https://<your domain>/<bundle id>/<screen label>

That URL is what per-page reporting and placement optimization key on, so the screen label is what you will see in the Ezoic dashboard.

Automatic tracking

The native SDKs track pageviews automatically, but they only see native screens. In a React Native app that is the single host Activity or view controller, so without labels every JavaScript screen lands in one bucket.

Labeling screens

Call trackPageview(screen) when the user reaches a screen to give it a name. With React Navigation, call it from the container's onStateChange:

import { useRef } from 'react';
import {
  NavigationContainer,
  useNavigationContainerRef,
} from '@react-navigation/native';
import { EzoicAds } from '@ezoic/react-native-sdk';

export default function App() {
  const navigationRef = useNavigationContainerRef();
  const lastRouteName = useRef<string | undefined>(undefined);
  const trackCurrentRoute = () => {
    const name = navigationRef.getCurrentRoute()?.name;
    if (name && name !== lastRouteName.current) {
      lastRouteName.current = name;
      EzoicAds.trackPageview(name);
    }
  };
  return (
    <NavigationContainer
      ref={navigationRef}
      onReady={trackCurrentRoute}
      onStateChange={trackCurrentRoute}
    >
      {/* ... */}
    </NavigationContainer>
  );
}

Labels are free text. Use / for hierarchy (members/profile); spaces become -, punctuation is dropped, and case is kept. The label is also attached to every ad request on that screen until the next pageview. A labeled pageview takes precedence over the automatic one for the same navigation, so there is no double counting. trackPageview() without a label records an unlabeled pageview.

Label your launch screen early. If you label the first screen, call trackPageview(screen) before EzoicAds.initialize or right after it resolves. A launch-screen label that arrives more than about 1 second after initialization finishes (outside a consent dialog) records a second launch pageview.

Manual tracking only

If you label every screen, turn off automatic tracking so pageviews come only from your calls:

await EzoicAds.initialize({ domain: 'example.com', autoTrackPageviews: false });

Troubleshooting

SDK Not Initializing

  1. Confirm the configured domain matches your Ezoic dashboard.
  2. Confirm the device or simulator has network access.
  3. Enable debugEnabled: true and review native logs: Logcat for EzoicAds on Android, the Xcode console on iOS.
  4. On iOS, confirm pod install ran after installing the package and that the build uses the generated workspace.

Ads Not Loading

  1. Make sure EzoicAds.initialize runs and resolves before EzoicBannerView or EzoicNativeAdView mounts.
  2. Confirm the Ezoic ad unit identifier is configured in Ezoic and passed as a string.
  3. Confirm the Google Mobile Ads application ID is present in AndroidManifest.xml (Android) and Info.plist (iOS).
  4. Give the banner explicit style dimensions large enough for the requested size.
  5. If Android banners come back 320x50 whatever size you pass, upgrade to React Native SDK 1.13.3; earlier versions started the load before the size prop was applied.
  6. Check consent configuration if your traffic is subject to privacy regulations. An error code of 5001 (EzoicErrorCode.consentRequired) means the built-in consent dialog is waiting for a decision. See Handling error 5001.

Build Failures

  1. Android: confirm google() and mavenCentral() are available to your app's Gradle repositories.
  2. iOS: delete ios/Pods and ios/Podfile.lock, then run pod install again.
  3. Confirm your React Native version meets the minimum requirement and that autolinking is enabled.