Add iAB Standard Size Experiences to an Android app
Show tag-driven IAB ad slots in an Android app with the iAB Standard Size Experience SDK 0.v3.11.
The iAB Standard Size Experience SDK renders IAB ad units (Interactive Advertising Bureau standard sizes such as 300×250 and 320×50) as Jetpack Compose composables. You add four Gradle artifacts, initialize the SDK with your API key, and place one composable per ad slot with a tag ID. The SDK fetches the creative, plays video and audio ads through Google Interactive Media Ads (IMA), tracks impressions and viewability, and reports analytics on its own.
A tag ID identifies one ad placement configured in Brand Control Center, so campaigns, creatives, and targeting change without an app update. This page covers the native Android SDK. The React Native wrapper is described in Add iAB Standard Size Experiences to a React Native app.
| Artifact | AAR size | Adds | Required |
|---|---|---|---|
| com.begenuin:genuin-sdk | 8 KB | GenuinSDK.initialize, setLoggingEnabled, setGeoLocation | Yes |
| com.begenuin:feed | 2.7 MB | The eight ad view composables in com.begenuin.feed.views | Yes |
| com.begenuin:instream-ads | 69 KB | The ad runtime: ad retrieval, VAST playback through IMA, viewability, macro resolution. No public API; core-v3 discovers it at runtime through ServiceLoader. | Yes |
| com.begenuin:core-v3 | 3.3 MB | Shared public types (BGInstreamAdEvent, BGGeoLocationInfo, GenuinEnv, AudioBehaviorConfig), networking, the player, analytics | Yes |
| com.begenuin:pangle-ads | 57 KB, plus the Pangle SDK 8.3.0.3 | Fills slots from Pangle (ByteDance) | Optional |
| com.begenuin:gma-ads | 58 KB, plus the Google Mobile Ads SDK 1.3.1 | Fills slots with Google native ads | Optional |
Sizes are the published AAR files before their dependencies. Keep every artifact on the same version.
Requirements
| Minimum | |
|---|---|
| Android | API 24 (Android 7.0). Every module declares minSdkVersion 24. |
| compileSdk and targetSdk | 36 |
| Android Gradle Plugin | 8.9.1 or later. The SDK itself is built with 8.13.2. |
| Gradle | 8.13, or the version your AGP requires |
| Kotlin | 2.0.21 or later. The AARs carry Kotlin 2.0 metadata, which Kotlin 1.9 can't read. |
| Java | 17 for sourceCompatibility, targetCompatibility, and jvmTarget |
| UI toolkit | Jetpack Compose, BOM 2024.09.00 or later. View-based apps host the composables in a ComposeView. |
| Core-library desugaring | Required by the Media3 IMA extension |
| Devices | Phones, tablets, and foldables on arm64-v8a, armeabi-v7a, and x86_64. Pangle fills on arm64-v8a and armeabi-v7a only. |
You also need an API key and at least one tag ID. See Step 5.
Step 1: Add the repositories
The four artifacts are on Maven Central, and their dependencies come from Google's repository and Maven Central. Declare both in your project settings:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
// Only with com.begenuin:pangle-ads. Pangle isn't on Maven Central.
maven {
url = uri("https://artifact.bytedance.com/repository/pangle/")
content { includeGroupByRegex("com\\.pangle.*") }
}
}
}A POM can't declare a repository, so the app has to. Without the ByteDance repository, a build that includes pangle-ads fails to resolve com.pangle.global:pag-sdk.
Step 2: Add the dependencies
Declare one version and apply it to every artifact:
val genuinVersion = "0.v3.11"
dependencies {
implementation("com.begenuin:genuin-sdk:$genuinVersion")
implementation("com.begenuin:feed:$genuinVersion")
implementation("com.begenuin:core-v3:$genuinVersion")
implementation("com.begenuin:instream-ads:$genuinVersion") // never imported; required at runtime
// implementation("com.begenuin:pangle-ads:$genuinVersion") // optional: Pangle fill
// implementation("com.begenuin:gma-ads:$genuinVersion") // optional: Google Mobile Ads fill
}Three things about this block:
- Declare
core-v3yourself.feedlists it at runtime scope only. Without your own line,BGInstreamAdEventandBGGeoLocationInfoaren't on the compile classpath and the build fails with an unresolved reference. instream-adshas no public API.core-v3finds it throughServiceLoaderat runtime. Without it, every slot renders "No ads available" and nothing is logged.- The ad-network modules need no code. Add
pangle-adsorgma-adsonly if Genuin has enabled that network for your tags. Each module registers itself throughMETA-INF/services; there's no initialization call. Don't add the Pangle or Google Mobile Ads SDKs yourself, because they arrive transitively at the versions the SDK is tested with.
Caution: Pin the exact version. genuin-sdk, feed, and core-v3 also publish a stray 0.v.1.q that Maven Central reports as latest, so a + range or a version-catalog latest.release resolves to the wrong artifact.
The SDK brings Media3 1.9.1 (ExoPlayer, HLS, and the IMA extension), Compose BOM 2024.09.00, and Material 1.13.0. It also pulls in Retrofit 2.9.0, kotlinx.serialization 1.7.3, Coil 2.6.0, Lottie Compose 4.0.0, Dagger 2.59.2, and the RudderStack Kotlin SDK 1.1.0. If your app already uses any of these, Gradle resolves to the higher version.
After a sync, confirm the wiring with ./gradlew :app:dependencies --configuration releaseRuntimeClasspath. The tree must show the four com.begenuin artifacts and androidx.media3:media3-exoplayer-ima.
Step 3: Enable Java 17, Compose, and desugaring
Ad playback runs on Google IMA, which arrives through androidx.media3:media3-exoplayer-ima:1.9.1. IMA uses java.time, so each app module must enable core-library desugaring itself; Gradle doesn't inherit it from a library.
plugins {
id("org.jetbrains.kotlin.plugin.compose") // the Kotlin 2.x Compose compiler
}
android {
buildFeatures { compose = true }
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}Without desugaring, the build fails with Invoke-customs are only supported starting with Android O (--min-api 26), or the app crashes on the first ad with NoClassDefFoundError: Failed resolution of: Ljava/time/Duration;.
Step 4: Configure the manifest
The library manifests don't declare network permissions. Add both to your AndroidManifest.xml, and register the Application subclass you'll initialize from:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:name=".MyApp">
<!-- your activities -->
</application>ACCESS_NETWORK_STATE backs the SDK's connectivity check. It also arrives transitively today, so declare it anyway in case those dependencies change.
Two things merge in from the SDK without any work on your side:
- The advertising ID permission.
instream-adsdeclarescom.google.android.gms.permission.AD_IDto resolve the[DID],[LIMITED_AD_TRACKING], and[DNT]macros in VAST tags. It's granted at install with no prompt. Declare advertising-ID use in the Play Console Data safety form, or the submission is rejected. To drop the permission, and with it those macros, add<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />. - Two activities.
BGFullScreenActivityfromfeedpresents fullscreen playback, andWebViewActivityfromcore-v3opens CTA landing pages in an in-app browser.
Ad assets travel over HTTPS, so no network-security-config or usesCleartextTraffic is needed. The SDK never shows a consent or tracking prompt. In a GDPR or ePrivacy region, collect consent through your own consent management platform before you request ads.
Step 5: Get your credentials
| Credential | Purpose | Where to get it |
|---|---|---|
| API key | Authenticates your app. Passed once to initialize. | Brand Control Center: Build > Publish Brand Community > SDK Integration, where the Live Environment and QA Environment tabs each show their key |
| Tag ID | Identifies one ad placement. Passed per ad slot. | The id of an iAB Standard Tag created in Brand Control Center under Grow > Off Site > Extended > iAB Standard Tag and accepted for your brand. Your Genuin team passes it to you. |
Keys aren't shared between environments: a Live key with GenuinEnv.QA returns no ads. Keep the key out of source control. Inject it from local.properties into a BuildConfig field, from a Gradle secrets plugin, or from your CI secrets.
Step 6: Initialize the SDK
Call GenuinSDK.initialize once, in Application.onCreate, before any ad slot composes. A slot that composes first throws IllegalStateException: SDK not initialized.
import android.app.Application
import com.begenuin.core.enums.GenuinEnv
import com.begenuin.core.model.AudioBehaviorConfig
import com.begenuin.core.model.AudioFocusLossHandling
import com.begenuin.sdk.GenuinSDK
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
GenuinSDK.initialize(
context = this,
apiKey = BuildConfig.GENUIN_API_KEY, // injected at build time, never a literal
env = GenuinEnv.PROD, // GenuinEnv.QA only with a QA key
fontFamily = "", // optional
audioBehaviorConfig = AudioBehaviorConfig(
duckVolumeDuringTransientCanDuck = 0.15f,
focusLossHandling = AudioFocusLossHandling.MUTE_OUTPUT
)
)
GenuinSDK.setLoggingEnabled(BuildConfig.DEBUG)
}
}| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| context | Application | Yes | — | Your application instance |
| apiKey | String | Yes | — | The brand API key from Step 5 |
| env | GenuinEnv | Yes | — | PROD or QA. Must match the environment the key and the tag belong to. |
| fontFamily | String | No | "" | A font family for text the SDK renders. Empty keeps the SDK default. |
| audioBehaviorConfig | AudioBehaviorConfig | No | AudioBehaviorConfig.DEFAULT | How playback reacts to audio-focus changes |
AudioBehaviorConfig has two fields:
| Field | Type | Default | Description |
|---|---|---|---|
| duckVolumeDuringTransientCanDuck | Float | 0.15f | Volume held while another app takes transient audio focus. Must be between 0 and 1; any other value throws IllegalArgumentException at initialization. |
| focusLossHandling | AudioFocusLossHandling | MUTE_OUTPUT | MUTE_OUTPUT keeps playing with the output muted; PAUSE_PLAYBACK pauses the ad. |
setLoggingEnabled(true) turns on the SDK's diagnostic log under the GenuinSDK logcat tag. The log is off in every published build whatever your own build type, and errors are logged either way. Gate it on debug builds, because the log names your tag IDs.
Step 7: Display an ad
Pick the composable that matches the slot size, place it in your layout, and pass the tag ID. The SDK requests the feed, renders the creative, and tracks the impression.
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.begenuin.feed.views.BG300x250AdView
@Composable
fun ArticleScreen() {
Column {
ArticleBody()
BG300x250AdView(
tagId = "{{TAG_ID}}",
modifier = Modifier.fillMaxWidth(),
)
}
}All eight ad views share one signature:
@Composable
fun BG300x250AdView(
tagId: String,
modifier: Modifier = Modifier,
uniqueId: String = "",
geoLocation: BGGeoLocationInfo? = null,
onEvent: (BGInstreamAdEvent) -> Unit = {},
)| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| tagId | String | Yes | — | The tag to load. A blank value shows the placeholder, makes no request, and emits no event. |
| modifier | Modifier | No | Modifier | Placement only (padding, alignment). Seven formats draw at their own dp size inside it; BGFSLandscapeAdView fills it. |
| uniqueId | String | No | "" | Set a different value for each slot that shares a tag and size on one screen. Per-slot state is keyed on iab_<tagId>_<format>_<uniqueId>. |
| geoLocation | BGGeoLocationInfo? | No | null | Geo targeting for this slot only. See Step 8. |
| onEvent | (BGInstreamAdEvent) -> Unit | No | {} | Lifecycle events. See Step 9. |
The slot keeps its declared size in every state, so your layout doesn't shift while the ad resolves. It shows a shimmer while loading, the ad pager once the feed resolves, and a dark box reading "No ads available" when it doesn't. The SDK never collapses an empty slot. To reclaim the space, handle NoAds in onEvent and hide the container yourself.
Host an ad in a View-based screen
Wrap the composable in a ComposeView. Your module still needs the Compose plugin and buildFeatures { compose = true } from Step 3.
<androidx.compose.ui.platform.ComposeView
android:id="@+id/adSlot"
android:layout_width="match_parent"
android:layout_height="wrap_content" />findViewById<ComposeView>(R.id.adSlot).setContent {
BG320x50AdView(tagId = "{{TAG_ID}}")
}The composition must run inside an Activity. Fullscreen and the CTA link-out read LocalContext as an Activity; with an application context the fullscreen button has no effect and the link-out throws.
Ad formats
| Composable | Slot size (dp) | Presentation | Notes |
|---|---|---|---|
| BG320x50AdView | 320 × 50 | Row | Standard banner |
| BG320x100AdView | 320 × 100 | Row | Large banner |
| BG300x250AdView | 300 × 250 | Full-bleed | Medium rectangle |
| BGCollapsibleAdView | 300 × 250, collapsing to 300 × 50 | Full-bleed, then row | A chevron in the control bar collapses and re-expands the slot with a 300 ms animation. Playback continues while collapsed. Each toggle emits Collapsed. |
| BG320x480AdView | 320 × 480 | Full-bleed | Half-page portrait |
| BG300x600AdView | 300 × 600 | Full-bleed | Half-page filmstrip |
| BG1280x720AdView | 1280 × 720 | Landscape | Fixed 16:9. A host narrower than 1280 dp clamps the width, so give it a wide box. |
| BGFSLandscapeAdView | Fills its box | Landscape | The same landscape design scaled to the box the modifier gives it. Bound one axis and the other follows at 16:9. |
Seven formats render at their IAB size in dp regardless of the container. A host narrower than the slot clamps it rather than letting it overflow. Give each placement at least the slot's width: 320 dp for the 320-wide slots and 300 dp for the 300-wide ones.
BGFSLandscapeAdView honours the ratio you ask for between 1.25:1 and 2.4:1. A box outside that band is letter-boxed or pillar-boxed to the nearest allowed ratio, so the slot can render smaller than its box.
The eight formats map to three presentations, chosen by the composable you place:
| Presentation | Formats | What the viewer sees |
|---|---|---|
| Full-bleed | 300 × 250, the expanded collapsible, 320 × 480, 300 × 600 | The creative scaled to the whole slot. Video ads show the brand logo top-left and the control bar top-right; audio ads show a brand-coloured background with a centred caption, an "Ad · remaining" pill, and a progress bar. |
| Row | 320 × 100, 320 × 50, the collapsed collapsible | The playing video on the left with the brand name, description, and the Watch and CTA buttons beside it |
| Landscape | 1280 × 720, flexible landscape | Audio ads show a centred portrait video panel flanked by advertiser and QR cards; video creatives render full-bleed. A box too narrow for the side cards falls back to full-bleed. |
Fullscreen, mute, and play/pause controls appear in every size unless the tag's configuration in Brand Control Center hides them. The two landscape formats skip the loading shimmer and stay on a plain backdrop until the ad is ready.
Step 8: Supply geo targeting (Optional)
By default, the SDK derives the viewer's location from the request IP, so there's nothing to do. If your app knows the location more precisely, pass it, and each field you set replaces the IP-derived value. Most apps set it once, app-wide, wherever the location becomes known:
import com.begenuin.core.model.BGGeoLocationInfo
import com.begenuin.sdk.GenuinSDK
GenuinSDK.setGeoLocation(
BGGeoLocationInfo(
country = "US",
region = "NY",
city = "New York",
dmaCode = "501",
latitude = 40.7128,
longitude = -74.0060,
)
)setGeoLocation is independent of initialize: call it before or after slots compose, from any thread, as often as the value changes. Pass null to clear it. For one placement that needs different targeting, pass geoLocation to that ad view; its fields win over the app-wide value one by one.
| Field | Type | Macros resolved | Fallback |
|---|---|---|---|
| country | String? | [COUNTRY_ID], [USER_GEO_COUNTRY] | IP lookup |
| region | String? | [REGION], [USER_GEO_REGION] | IP lookup |
| city | String? | [CITY], [USER_GEO_CITY] | IP lookup |
| dmaCode | String? | [DMA_CODE], [USER_GEO_DMA] | None. DMA targeting happens only if you pass it. |
| latitude | Double? | [LOCATION_LAT], [USER_GEO_LAT] | IP lookup |
| longitude | Double? | [LOCATION_LON], [USER_GEO_LON] | IP lookup |
Three rules govern how values resolve. The slot's geoLocation wins over the app-wide value, which wins over the IP lookup, field by field; a null or blank field falls through to the next source. Each ad request reads the geo current when it builds its URL, so a late location still reaches every request that hasn't gone out, prefetched ones included. Requests already in flight keep their geo; no ad is re-requested.
Setting both latitude and longitude also reports the geo type ([USER_GEO_TYPE], [DEVICE_GEO_TYPE]) as device location rather than IP-derived. The SDK never asks for the location permission and never reads device location itself. If you pass coordinates, your app owns the permission, the consent, and the matching Data safety declaration.
Step 9: Observe lifecycle events
Pass an onEvent lambda to any ad view. The SDK emits BGInstreamAdEvent values as the slot loads its feed and plays through its ads:
BG300x250AdView(
tagId = "{{TAG_ID}}",
onEvent = { event ->
when (event) {
BGInstreamAdEvent.Loading -> {} // feed request started
is BGInstreamAdEvent.Loaded -> {} // event.count ads to play
BGInstreamAdEvent.NoAds -> hideAdContainer() // reclaim the space yourself
is BGInstreamAdEvent.Appeared -> {} // ad event.index of event.total is on screen
is BGInstreamAdEvent.Started -> {} // playback began
is BGInstreamAdEvent.Completed -> {} // this ad played to the end
is BGInstreamAdEvent.Changed -> {} // pager moved event.from -> event.to
is BGInstreamAdEvent.LoadTimeout -> {} // still loading after event.timeoutMs
is BGInstreamAdEvent.NoFill -> {} // this ad's waterfall produced nothing
is BGInstreamAdEvent.AllAdsCompleted -> {} // nothing left to play in the slot
is BGInstreamAdEvent.Collapsed -> {} // event.isCollapsed is the new state
}
},
)BGInstreamAdEvent is a sealed interface with 11 cases. Ad positions are 1-based.
| Event | Payload | Fires when |
|---|---|---|
| Loading | — | The slot began loading its feed |
| Loaded | count: Int | The feed resolved to count renderable ads |
| NoAds | — | The feed resolved but produced nothing renderable, the request failed, or instream-ads is missing |
| Appeared | index, total, type | Ad index of total became the active cell |
| Started | index, total, type | The creative resolved and playback began |
| Completed | index, total, type | Ad index played its media to the end. Only a real playout reports it; an ad that never filled reports NoFill instead. |
| Changed | from, to | The pager moved between ads |
| LoadTimeout | index, total, type, timeoutMs: Long | Ad index hadn't resolved within its 500 ms waterfall budget. Informational only: the waterfall continues and still reports Started or NoFill. |
| NoFill | index, total, type | Ad index was tried but its waterfall produced no fill |
| AllAdsCompleted | total: Int | The last ad in the slot finished or failed to fill, so nothing is pending. Fires once per pass; a revisited slot that resolves a fresh ad reports it again. |
| Collapsed | isCollapsed: Boolean | The viewer collapsed or expanded BGCollapsibleAdView. Never fires for the fixed sizes. |
type labels the kind of ad the slot led with. The values are audio (a VAST audio ad), external (filled by Pangle or Google Mobile Ads), sponsored (a sponsored post), video (a video-content ad), and unknown. Mute changes, Watch and CTA taps, and fullscreen enter and exit aren't surfaced as events; the SDK reports them through its own analytics (see Analytics events).
Step 10: Keep ads working in release builds
The 0.v3.11 AARs ship no consumer ProGuard rules; their proguard.txt files are empty. Two mechanisms inside the SDK break under R8 full mode, the default since AGP 8. The ServiceLoader registrations in META-INF/services name provider classes as text, and Retrofit reflects on generic signatures that R8 strips. The symptom is "No ads available" in release builds only, with ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType in logcat and an "Ad feed request returned no data" line after a few milliseconds.
Add these rules to your app's proguard-rules.pro:
#--- iAB Standard Size Experience SDK: providers and serializers are resolved by class name
-keep class com.begenuin.** { *; }
-keep interface com.begenuin.** { *; }
-dontwarn com.begenuin.**
#--- Google IMA (VAST playback)
-keep class com.google.ads.interactivemedia.** { *; }
-dontwarn com.google.ads.interactivemedia.**
#--- Retrofit under R8 full mode: keep the generic signatures it reflects on
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keepattributes Signature, InnerClasses, EnclosingMethod, RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>These are the rules the React Native wrapper ships as consumer rules and checks against a minified release build. The broad com.begenuin.** keep trades APK size for certainty. Verify ad rendering in a build with isMinifyEnabled = true before you release.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Build fails with Module was compiled with an incompatible version of Kotlin | Your Kotlin plugin is below 2.0.21 | Upgrade the Kotlin Gradle plugin to 2.0.21 or later |
| Build fails with class file has wrong version 61.0 or Inconsistent JVM-target compatibility | Java or JVM target below 17 | Set sourceCompatibility, targetCompatibility, and jvmTarget to 17 (Step 3) |
| Invoke-customs are only supported starting with Android O, or NoClassDefFoundError for a java.time class at runtime | Core-library desugaring is off | Enable desugaring and add desugar_jdk_libs (Step 3) |
| Unresolved reference: BGInstreamAdEvent | core-v3 isn't declared | Add com.begenuin:core-v3 (Step 2) |
| Could not resolve com.begenuin:... | google() or mavenCentral() is missing | Declare both repositories (Step 1) |
| Could not find com.pangle.global:pag-sdk | The ByteDance repository is missing | Add it to settings.gradle.kts (Step 1) |
| Crash with IllegalStateException: SDK not initialized | A slot composed before initialize ran | Initialize in Application.onCreate (Step 6) |
| Every slot shows "No ads available" and no Loading event fires | The tagId is blank | Pass a non-empty tag ID |
| Every slot shows "No ads available" after Loading | instream-ads isn't on the classpath, the key or env is wrong, or the tag is inactive | Check the dependency, the key and environment pair, and the tag; turn on setLoggingEnabled(true) and read the GenuinSDK tag |
| Ads never load and no network request is made | INTERNET isn't declared | Add the permission (Step 4) |
| Ads work in debug but not in a minified release build | R8 stripped provider registrations or Retrofit signatures | Add the keep rules (Step 10) |
| The fullscreen button has no effect, or the CTA crashes | The composition isn't hosted in an Activity | Host the ad in an Activity-backed composition |
| Pangle or Google Mobile Ads never fill | The module is missing, Pangle is on an x86 emulator, or your package isn't registered with Pangle | Look for BGAdNetworkRegistry: ad networks available = [...] in the log; test Pangle on an arm64 device. Pangle error 40060 means the app isn't registered on Pangle's platform, not a no-fill. |
App size
| Artifact | AAR size |
|---|---|
| genuin-sdk | 8 KB |
| feed | 2.7 MB |
| instream-ads | 69 KB |
| core-v3 | 3.3 MB |
| Total, required modules | 6.0 MB before dependencies |
Media3 and the Google IMA SDK add to that, and are shared if your app already uses them. The optional pangle-ads and gma-ads modules each add their network's SDK. Enabling R8 reduces all of these figures.
Analytics events
The SDK reports the ad lifecycle through its own analytics pipeline (RudderStack) with no wiring on your side. It sends 21 events, which appear in Brand Control Center reporting and in any analytics destination you connect.
| Group | Events |
|---|---|
| Tag | Tag Initialized, Tag Rendered, Tag Viewed |
| Request | Ad Requested, Ad Response Received, Ad Request Failed, Api Failure |
| Rendering | Ad Impression, Ad Viewable Impression, Ad Rendered, Ad Rendered Failed |
| Playback | Ad Media Play, Ad Played, Ad Paused, Ad Media Quartile, Ad Completed, Playback Failed |
| Interaction | Ad Muted, Ad Unmuted, Ad Clicked |
| Errors | Ad Error |
Limitations
- Android only, API 24 and later: there's no View-based ad view; View apps host the composables in a
ComposeView. - One version for all artifacts:
genuin-sdk,feed,instream-ads, andcore-v3must share a version, and a+range resolves to the wrong artifact. - Fixed slot sizes: seven formats draw at their IAB dp size and clamp in a narrower host; only
BGFSLandscapeAdViewadapts, and only between 1.25:1 and 2.4:1. - No pause or resume API: playback follows the Android lifecycle of the host Activity.
- No consumer ProGuard rules in 0.v3.11: add the rules in Step 10 or release builds show no ads.
- Pangle is arm-only and always personalised: it never fills on x86 emulators, and the module requests personalised ads without a consent signal. Talk to Genuin before shipping Pangle in a consent region.
- Empty slots stay on screen: the SDK doesn't collapse a slot with no ads; handle
NoAdsyourself.

