Skip to content
Nitx Developers

Nitx Publisher SDK

React Native SDK

TypeScript bridge over the native Android and iOS SDKs, with banner and native ad components.

TypeScript
@nitx/publisher-react-native
React Native 0.75+
Android minSdk 24
iOS 15+
Updated Oct 29, 2026

@nitx/publisher-react-native is the React Native bridge of the Nitx Publisher SDK (Protocol §10, v1.2). It is a bridge only: every call goes through the platform channel to the native Android SDK (io.nitx:publisher, Kotlin) or iOS SDK (NitxPublisher, Swift), which hold all ad logic, normalisation, hashing and storage. JavaScript carries values and renders nothing itself.

  • TypeScript, React Native 0.75 or newer, old-architecture native modules and view managers (they run unchanged under the New Architecture interop layer; Turbo modules and Fabric components are not required).
  • Pinned native SDK: 1.0.0 on both platforms. context.sdk reports both versions, for example rn-1.0.0/android-1.0.0 and rn-1.0.0/ios-1.0.0 (Protocol §10.7).

Install

Shell
npm install @nitx/publisher-react-native

Autolinking registers the native module and the two view managers through react-native.config.js (NitxPackage on Android, the NitxPublisherReactNative pod on iOS). No manual linking.

Android

Nothing to configure for a published app. The bridge's android/build.gradle depends on io.nitx:publisher:1.0.0; the coordinate is the same on Maven Central once published. Until then it comes from Maven local (./gradlew :nitx-publisher:publishToMavenLocal in the Android SDK repository) and the app's root android/build.gradle must let every project see it, because Gradle resolves transitive dependencies with the consuming project's repositories:

Groovy
allprojects {
    repositories {
        mavenLocal()
    }
}

Requirements inherited from the native SDK: minSdk 24, Kotlin 1.9 or newer, the INTERNET permission (declared by the SDK). Local http backends need android:usesCleartextTraffic="true" on the application element of the app only.

iOS

Shell
cd ios && pod install

The pod NitxPublisherReactNative depends on NitxPublisher ~> 1.0.0 and iOS 15.0 or newer, so the app's Podfile needs platform :ios, '15.0' (the React Native template default is lower). While the NitxPublisher pod is not published, point the Podfile at a checkout of the iOS SDK:

Ruby
pod 'NitxPublisher', :path => '../../nitx-publisher-sdk-ios'

The SDK ships PrivacyInfo.xcprivacy in its resource bundle. Clicks open in SFSafariViewController; the IDFA is read only after the app's own App Tracking Transparency prompt.

Start

Call Nitx.start once, early (module scope of your root component or the app entry file). The native SDK ignores a second call.

TypeScript
import { Nitx } from '@nitx/publisher-react-native';

Nitx.start('SDK-AM-XXXXX', {
  api: 'https://api.nitx.app', // base URL override (optional)
  debug: __DEV__,              // logcat tag Nitx / unified log io.nitx.publisher
  personalization: true,       // false: no storage, no visitor block; consent, identify, reset are no-ops
});

Nitx.setScreen('HomeFeed');    // optional, sent as context.app.screen
const state = await Nitx.state();   // snapshot: counters, mode, consent, visitor id, hashed profile keys
console.log(state.sdk);             // rn-1.0.0/android-1.0.0 or rn-1.0.0/ios-1.0.0
console.log(Nitx.versions());       // { bridgeVersion, nativeVersion, sdk, platform }

A mobile source has one code per platform; pick it with Platform.OS. The SDK loads GET /api/ad-mobiles/show/{code}, warns when the registered bundle_id differs from the running package or bundle identifier, and activates the source once while it is pending_verification.

Formats

Slot kinds come from the slot's format_type in the dashboard: banner_320x50, banner_300x250, banner_adaptive, interstitial, app_open, native, rewarded (v2).

TSX
import { NitxBanner } from '@nitx/publisher-react-native';

<NitxBanner
  slot="SLOT-XXXXXX"
  style={{ alignSelf: 'center' }}
  onLoaded={() => {}}
  onFailed={(error) => console.log(error.code, error.message)}
  onImpression={() => {}}
  onClicked={() => {}}
/>

The native view sizes itself from the slot kind and reports that size to JavaScript through a native event, so the component takes 320x50 or 300x250 dp for the fixed kinds and, for banner_adaptive, stretches to its wrapper's width (measured with onLayout and handed to the native view as the available width) with the height the SDK picks for that width. Until an ad renders, and after a failure, the component is 0 dp high. style applies to the wrapper; use it for alignment and margins rather than for a size. Image, video (muted, looping) and html creatives are rendered natively; html uses a WebView.

Interstitial

TypeScript
import { NitxInterstitial } from '@nitx/publisher-react-native';

const { id } = await NitxInterstitial.load('SLOT-XXXXXX'); // resolves once the creative is ready
const subs = [
  NitxInterstitial.addListener('shown', (e) => {}),
  NitxInterstitial.addListener('clicked', (e) => {}),
  NitxInterstitial.addListener('dismissed', (e) => { /* e.id, e.slot */ }),
  NitxInterstitial.addListener('failed', (e) => console.log(e.error)),
];

// later, at a natural break
if (await NitxInterstitial.isReady(id)) {
  await NitxInterstitial.show(id); // resolves true when presented
}
subs.forEach((s) => s.remove());

load rejects with { code, message } (no_fill, network, not_started, render, unavailable, invalid) when nothing is ready; failed events cover failures while showing. Each id shows at most once and leaves the native registry after dismissed; NitxInterstitial.destroy(id) drops one that will not be shown. The full-screen view shows a close control after at most 5 seconds and when a video ends.

App open

TypeScript
import { NitxAppOpen } from '@nitx/publisher-react-native';

// Manual
await NitxAppOpen.load('SLOT-XXXXXX');
const shown = await NitxAppOpen.showIfAvailable();

// Automatic: on every return to the foreground, at most once per 60 seconds, one kept preloaded
NitxAppOpen.enableAutoShow('SLOT-XXXXXX', 60);
NitxAppOpen.disableAutoShow();

NitxAppOpen.addListener('shown', () => {});
NitxAppOpen.addListener('dismissed', () => {});
// also: loaded, clicked, failed

Native

The SDK supplies assets only. The app renders them with its own components inside a NitxNativeAdView carrying the ad id, and destroys the ad when the row leaves for good.

TSX
import { NitxNativeAd, NitxNativeAdView, type NitxNativeAdHandle } from '@nitx/publisher-react-native';

function SponsoredRow({ slot }: { slot: string }) {
  const [ad, setAd] = useState<NitxNativeAdHandle | null>(null);

  useEffect(() => {
    let id: string | null = null;
    NitxNativeAd.load(slot).then((handle) => { id = handle.id; setAd(handle); }).catch(() => {});
    return () => { if (id) NitxNativeAd.destroy(id); };
  }, [slot]);

  if (!ad) return null;
  const a = ad.assets; // headline, body, cta, advertiser, icon, image, video, rating, price: all nullable
  return (
    <NitxNativeAdView adId={ad.id} style={styles.card}>
      <Text>Sponsored{a.advertiser ? ` by ${a.advertiser}` : ''}</Text>
      {a.image && <Image source={{ uri: a.image }} style={styles.image} />}
      <Text style={styles.headline}>{a.headline ?? 'Sponsored'}</Text>
      {a.body && <Text>{a.body}</Text>}
      {a.cta && ad.clickable && <Text style={styles.cta}>{a.cta}</Text>}
    </NitxNativeAdView>
  );
}

How the container works: NitxNativeAdView is a native view group (a ReactViewGroup subclass on Android, an RCTView subclass on iOS), so React Native lays out its children and every View prop applies. Once it is attached to a window with an adId, the native side looks the id up in its registry and calls the SDK's NitxNativeAd.registerView(container, [container]): the SDK records the impression, observes the container for viewability (50 percent visible for 1 second) and opens click_url when the container is tapped. The whole container is clickable; there is no clickable prop. On Android the container intercepts the native touch stream for a registered clickable ad (React Native's own touch handling happens at the root view and is unaffected); on iOS the SDK's tap recognizer does not cancel touches. NitxNativeAd.addListener('impression' | 'viewable' | 'clicked', ...) reports the interaction events (viewable is Android only; the iOS SDK delegate has no viewable callback). With today's backend a native ad is a path-only creative: headline is the ad name and image (or video) is the path.

Rewarded

NitxRewarded.load(slot) exists so apps can wire it; in v1 it rejects with code unavailable ("Rewarded ads are not available in v1") and show resolves false.

TypeScript
Nitx.consent(true);   // from your own consent UI; the SDK shows none
Nitx.identify({ email: 'user@example.com', phone: '+966501234567', gender: 'f', birthYear: 1990, traits: { plan: 'pro' } });
Nitx.identify({ phone: null });            // null deletes a stored field
Nitx.identify({ clear: ['email', 'traits'] });
Nitx.reset();                              // forgets the visitor on device and, when advertised, server side

JavaScript only validates and shapes the input (shapeTraits is exported for tests): wrong types and unknown fields throw a TypeError, null becomes a clear entry. Normalisation (email lowercased, phone to E.164, gender to m, f, other, birth year or age, city and country), SHA-256 hashing of email and phone, the 20-trait limit and the under-16 rule all run in the native SDK; raw values are never stored, logged or sent. The profile is persisted only once consent is true; without consent only the visitor id travels.

Versioning

The bridge pins one native version per platform (android/build.gradle and the podspec). The native modules pass the label rn-<bridge version> to the SDK's NitxOptions.bridge, and the SDK reports context.sdk as rn-1.0.0/android-1.0.0 or rn-1.0.0/ios-1.0.0 in every POST ads request. Nitx.versions().sdk and Nitx.state().sdk return the same string. A jest test checks that package.json, the Kotlin and the Swift label agree.

Contributing

The sections below are for working on the SDK itself: running it locally, testing and releasing. You don't need them to integrate.

Example app

example/ is a plain React Native 0.75 app that depends on the package through a local path. Its single screen shows a 320x50 banner, a 300x250 banner, a feed with a native ad every third row rendered by the app's own card inside NitxNativeAdView, interstitial and app open buttons, a consent switch, identify inputs, reset and the Nitx.state() readout. It starts the SDK with SDK-AM-DEMOA on Android and SDK-AM-DEMOI on iOS, API http://localhost:8001, debug: true, and uses the slots SLOT-A-* / SLOT-I-* (BAN50, MREC, NATIVE, INTER, OPEN).

Shell
# once
npm install && (cd example && npm install)
# backend on :8001 and the creative server on :4300 (nitx-publisher-sdk/local/start-stack.sh)

# Android: forward the local ports into the emulator, then build and run
adb reverse tcp:8001 tcp:8001 && adb reverse tcp:4300 tcp:4300 && adb reverse tcp:8081 tcp:8081
cd example && npx react-native run-android
adb logcat -s Nitx

# iOS: the simulator reaches the Mac's localhost directly
cd example/ios && pod install && cd .. && npx react-native run-ios

Build notes: the example's gradle wrapper is 8.10.2 with validateDistributionUrl=false, the Android SDK is resolved from Maven local, and the iOS deployment target is 15.0.

Development

Shell
npm install
npm run typecheck
npm test          # jest: traits shaping, event wiring with a mocked native module, banner sizing, version pinning
npm run build     # lib/ (commonjs, module, typescript declarations)

Package layout: src/ (TypeScript surface), android/ (Kotlin: NitxModule, NitxBannerViewManager, NitxNativeAdViewManager, NitxPackage), ios/ (Swift modules with ObjC RCT_EXTERN bridges), NitxPublisherReactNative.podspec, react-native.config.js, example/.

Generated from README.md in NitxTech/nitx-publisher-sdk-react-native at c8b7e40 · Oct 29, 2026