Skip to content

Nitx Publisher SDK

Android SDK

Native Kotlin library for banner, interstitial, app open and native ads.

Kotlin
io.nitx:publisher
minSdk 24
No third-party networking
Updated Oct 29, 2026

Native Android implementation of the Nitx Publisher SDK (Protocol §10, v1.2). Kotlin, minSdk 24, no third-party networking or image libraries. The sample module shows every format in a plain Activity.

Modules:

  • nitx-publisher: the library (io.nitx.publisher).
  • sample: a demo app (com.nitx.publisher.sample).

Install

Kotlin
dependencies {
    implementation("io.nitx:publisher:1.0.0")
    // Optional: lets the SDK read the Google advertising id (CONTRACT 10.4). Loaded reflectively;
    // apps without it, or without Play Services, keep working.
    implementation("com.google.android.gms:play-services-ads-identifier:18.1.0")
}

The library depends only on the Kotlin stdlib, kotlinx-coroutines-android, androidx.annotation, androidx.core and androidx.browser (Chrome Custom Tabs). JSON is parsed with org.json. Images are loaded with HttpURLConnection and BitmapFactory; videos play in VideoView.

Start

Call Nitx.start once, ideally from Application.onCreate:

Kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Nitx.start(this, "SDK-AM-XXXXX", NitxOptions(debug = BuildConfig.DEBUG))
    }
}

NitxOptions: api (base URL override, default https://api.nitx.app), debug (logcat tag Nitx), personalization (default true; false means no storage, no visitor block, and consent, identify, reset are no-ops), testMode (reserved for v2), bridge (label set by the React Native and Flutter bridges, for example rn-1.0.0; context.sdk then reports rn-1.0.0/android-1.0.0 as Protocol §10.7 asks; apps using the SDK directly leave it null).

On start the SDK loads GET /api/ad-mobiles/show/{code}, compares the registered bundle_id with the app's package name (a mismatch logs a warning and never blocks), and calls GET /api/ad-mobiles/init/{code} once per process only while the source is pending_verification. Every public call is safe on the main thread and never throws into the app; failures are logged in debug mode only.

Nitx.state() returns a snapshot (mode, counters, consent, visitor id, hashed profile) for debugging and tests; toString() prints it as JSON.

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).

XML
<io.nitx.publisher.NitxBannerView
    android:id="@+id/banner"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:slot="SLOT-XXXXXX" />
Kotlin
val banner = findViewById<NitxBannerView>(R.id.banner)
banner.listener = object : NitxBannerListener {
    override fun onAdLoaded(view: NitxBannerView) {}
    override fun onAdFailed(view: NitxBannerView, error: NitxError) {}
    override fun onAdClicked(view: NitxBannerView) {}
    override fun onAdImpression(view: NitxBannerView) {}
}
banner.load() // optional: it loads on attach; call load() to refresh

The view sizes itself from the slot kind (320x50 dp, 300x250 dp, or full width and 50 to 90 dp for banner_adaptive) unless the layout gives exact dimensions. Image, video (muted, looping) and html creatives are rendered natively; html uses a WebView.

Interstitial

Kotlin
val interstitial = NitxInterstitial.load(context, "SLOT-XXXXXX", object : NitxFullScreenCallback {
    override fun onAdLoaded() {}
    override fun onAdFailed(error: NitxError) {}
    override fun onAdShown() {}
    override fun onAdClicked() {}
    override fun onAdDismissed() {}
})

// later, at a natural break
if (interstitial.isReady) interstitial.show(activity)

The full-screen activity shows a close control after at most 5 seconds and when a video ends.

App open

Kotlin
// Manual
NitxAppOpen.load(context, "SLOT-XXXXXX")
NitxAppOpen.showIfAvailable(activity)

// Automatic: show on every return to the foreground, at most once per 60 seconds, keep one preloaded
NitxAppOpen.enableAutoShow(application, "SLOT-XXXXXX", 60)

Foreground detection uses ActivityLifecycleCallbacks; no lifecycle library is required.

Native

The SDK supplies assets only; the app renders them with its own layout and registers the view.

Kotlin
NitxNativeAd.load(context, "SLOT-XXXXXX", object : NitxNativeAdCallback {
    override fun onAdLoaded(ad: NitxNativeAd) {
        val a = ad.assets // headline, body, cta, advertiser, icon, image, video, rating, price: all nullable
        headline.text = a.headline ?: "Sponsored"
        body.text = a.body ?: ""
        cta.isVisible = a.cta != null && ad.isClickable
        cta.text = a.cta
        a.image?.let { loadWithYourImageLoader(it, imageView) }
        // The SDK tracks viewability on the container and clicks on the listed views.
        ad.registerView(container, listOf(imageView, cta, headline))
    }

    override fun onAdFailed(error: NitxError) {}
})

// when the row leaves for good
ad.destroy()

With today's backend a native ad is a path-only creative: headline is the ad name and image (or video for mp4 and webm) is the path. Structured assets are passed through when present.

Rewarded

NitxRewarded.load(context, slot, callback) exists so apps can wire it; in v1 it fails with "Rewarded ads are not available in v1" and show returns false.

Kotlin
Nitx.consent(true)   // from your own consent UI; the SDK shows none
Nitx.identify(NitxTraits(email = "user@example.com", phone = "+966501234567", gender = "f", birthYear = 1990))
Nitx.identify(NitxTraits.clearing("phone"))
Nitx.setScreen("HomeFeed")
Nitx.reset()         // forgets the visitor on device and, when the backend advertises it, server side

Normalisation runs on the device: email trimmed and lowercased, phone reduced to E.164 (numbers without a country code are dropped), gender mapped to m, f, other, birth year 1900 to the current year (or derived from age), city and country lowercased and cut at 64 characters, at most 20 traits with keys [a-z0-9_]{1,32}. Email and phone are hashed with SHA-256 at once; raw values are never stored, logged or sent. Calls implying an age under 16 are discarded. The profile is persisted only once consent is true; without consent only the visitor id travels.

Storage (SharedPreferences file nitx): nitx.visitor_id, nitx.consent, nitx.profile (hashes only), nitx.app_open_last_shown. The advertising id is read at start and after consent changes, held in memory only, and sent in context.ad_id only with consent.

Serving

Today creatives come from GET /api/ad-slots/show/{code} per slot: ads with a null path are ignored, filler_content = static shows the first ad, otherwise one is picked at random. The creative type is the ad's creative field when present, else by path extension (mp4 and webm are video, everything else image). Clicks open click_url in Chrome Custom Tabs (falling back to ACTION_VIEW); without click_url the creative is not clickable.

When the config response carries the sdk block, the SDK switches to POST {ads_endpoint} with the app context block of Protocol §10.4, batches impression and viewable events every 2 seconds (and on background) to {events_endpoint}, and posts {forget_endpoint} on reset. Paths that are not relative to the API base are ignored; a 404, 405 or 501 from the POST falls back to GET for the rest of the process.

Viewability: a banner or native ad is viewable after at least 50 percent of the view was visible for 1 second; interstitial and app-open ads are viewable when shown.

Permissions and Play data safety

The library manifest declares:

  • android.permission.INTERNET
  • com.google.android.gms.permission.AD_ID (remove it with tools:node="remove" if your app must not use the advertising id)

Play Console data safety entries for an app that ships the SDK:

DataCollectedSharedPurposeNotes
Email address (hashed)OptionalYes, with NitxAdvertising or marketingOnly after Nitx.consent(true) and only if the app calls identify; SHA-256 on device
Phone number (hashed)OptionalYes, with NitxAdvertising or marketingSame as above
Other personal info (gender, birth year, city, country, traits)OptionalYes, with NitxAdvertising or marketingOnly with consent and only if the app calls identify
Approximate locationYes (derived)NoAdvertising or marketingCoarse location from the IP address, computed server side; the SDK sends no location
Device or other IDs (advertising id, SDK visitor id)OptionalYes, with NitxAdvertising or marketingAdvertising id only with consent; visitor id is a random UUID the user can reset
App interactions (ad impressions and clicks)YesYes, with NitxAnalytics, advertising or marketingImpression and click events only, no page content

Data is encrypted in transit (https in production). Users can request deletion through the app's Nitx.reset() or Nitx support. The equivalent of Apple's PrivacyInfo.xcprivacy on Android is this table plus the AD_ID permission declaration; there is no manifest file to ship.

ProGuard and R8

consumer-rules.pro ships with the library and is applied automatically: it keeps the public API and the reflectively loaded AdvertisingIdClient. No app-side rules are needed.

Contributing

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

Local testing

The sample talks to a local backend on http://localhost:8001 and expects creative files on http://localhost:4300. On an emulator, forward both ports from the host first:

Shell
adb reverse tcp:8001 tcp:8001
adb reverse tcp:4300 tcp:4300
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
./gradlew :nitx-publisher:test :nitx-publisher:assembleRelease :sample:assembleDebug
adb install -r sample/build/outputs/apk/debug/sample-debug.apk
adb shell am start -n com.nitx.publisher.sample/.MainActivity
adb logcat -s Nitx

The sample starts the SDK with source SDK-AM-DEMOA, debug on, and uses the slots SLOT-A-BAN50, SLOT-A-MREC, SLOT-A-NATIVE, SLOT-A-INTER and SLOT-A-OPEN. Cleartext http is enabled in the sample manifest only (android:usesCleartextTraffic="true").

Unit tests (nitx-publisher/src/test) run on the JVM with JUnit 4 and an in-memory HTTP fake; fixtures under src/test/resources/fixtures are real backend responses.

Generated from README.md in NitxTech/nitx-publisher-sdk-android at bd6395f · Oct 29, 2026