Skip to content

What is AdMob CMP?

AdMob CMP is a KMP AdMob library — a Kotlin Multiplatform wrapper over the Google Mobile Ads SDKs that ships a single Kotlin API for the formats AdMob publishers actually use, on both Android and iOS. The Android side binds the GMA Next-Gen SDK (1.3.0) and the iOS side binds GMA 13.x through Kotlin/Native cinterop, so the same rememberAdManager() call, the same AdPlacement, and the same AdEvent stream work on both platforms. The published coordinate is dev.avinya.ads:admob-cmp under the dev.avinya.ads group, at version 1.1.0. The Android app runs on API 26 or above and the iOS app targets iOS 15 or above.

The library keeps AdMob’s vocabulary — AdValue, response info, adaptive banner sizes, UMP consent states, native asset names — but replaces the listener-style SDK surface with suspend functions, StateFlow state, and a single sealed AdEvent stream. You write the integration once and it runs identically on both platforms, with platform-specific behaviour isolated to thin Android and iOS adapters that the consumer never sees. Platform specifics stay where they belong: the Android app’s AndroidManifest.xml declares the AdMob app id, the iOS app’s Info.plist declares GADApplicationIdentifier, and everything in between is shared code.

The split is deliberate. The library is published as two modules behind a single coordinate: admob-cmp-core holds the controllers, state machines, and platform adapters, while admob-cmp-compose holds the composable surface. Most consumers depend on the umbrella dev.avinya.ads:admob-cmp artifact and never notice the split. Apps that want the controller API without Compose Multiplatform can depend on admob-cmp-core directly.

Every format in the current AdMob catalogue, behind a single controller-per-format API:

FormatControllerComposableGuide
Banner (incl. collapsible)adManager.banner(placement)BannerAdViewBanner ads
InterstitialadManager.interstitial(placement)Interstitial ads
RewardedadManager.rewarded(placement)Rewarded ads
Rewarded interstitialadManager.rewardedInterstitial(placement)Rewarded ads
App-openadManager.appOpen(placement) + AppOpenAdCoordinatorApp-open ads
NativeadManager.nativeAd(placement) (a pool)NativeAdViewNative ads

The controllers are placement-keyed and cached for the manager’s lifetime, so an app with three banner placements owns three BannerAdController instances — not a fresh one per recomposition. NativeAdView is the one exception in shape: it is a pool, because native ads are single-use.

What does it give you that the raw SDKs do not?

Section titled “What does it give you that the raw SDKs do not?”

Four things the library owns so the app does not have to:

  1. A placement-keyed controller cache so the same AdPlacement always resolves to the same instance with the same load state.
  2. A TTL’d FIFO ad cache with capped exponential retry, configurable per placement through AdCachePolicy, AdExpirationPolicy and AdRetryPolicy.
  3. A consent gate that makes a pre-consent ad request impossible by construction — every load checks canRequestAds and fails fast with AdErrorCode.CONSENT_REQUIRED when the user has not opted in.
  4. One event stream instead of five callback interfaces, with AdEvent covering Loaded, LoadFailed, Impression, Clicked, Paid, RewardEarned and the iOS-only video family.

The whole integration looks like this:

@Composable
fun App() {
val adManager = rememberAdManager()
LaunchedEffect(Unit) {
adManager.gatherConsentAndInitialize(
AdConfig(
androidAppId = TestAdIds.ANDROID_APP_ID,
iosAppId = TestAdIds.IOS_APP_ID,
)
)
}
val placement = remember {
AdPlacement(
id = "main_interstitial",
format = AdFormat.Interstitial,
androidAdUnitId = TestAdIds.ANDROID_INTERSTITIAL,
iosAdUnitId = TestAdIds.IOS_INTERSTITIAL,
)
}
val interstitial = remember(adManager) { adManager.interstitial(placement) }
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
interstitial.load()
interstitial.show()
}
}
) { Text("Show ad") }
}

Two caveats worth knowing on day one. show() suspends for the ad’s whole on-screen lifetime, so it must run in a UI-scoped coroutine — never in GlobalScope. And a second show() on the same controller while one is still on screen returns AdShowResult.NotReady rather than queueing; await the first result before calling again. Both rules are documented in detail on the interstitial guide.

The cache and retry layer is the second thing people underestimate. A naive integration against the raw SDKs opens a TCP connection, calls GMA’s load, waits on a listener, parses the response, and only then exposes state. Every app that ships a full-screen ad eventually writes that caching, eviction, and retry layer themselves — usually wrong, and usually twice (once per platform). The library writes it once in shared code, with the policy surfaces the consumer needs (AdCachePolicy, AdExpirationPolicy, AdRetryPolicy, AdTimeoutPolicy) and nothing else. The caching, retry and timeouts guide goes through every knob.

How does it compare with other Kotlin Multiplatform AdMob libraries?

Section titled “How does it compare with other Kotlin Multiplatform AdMob libraries?”

A neutral look at capability, not at quality:

CapabilityAdMob CMPTypical Kotlin Multiplatform AdMob wrapper
Banner, interstitial, rewardedYesYes
Rewarded interstitialYesVaries
App-open adsYesUsually not offered
Native adsYes, with a layout DSL and an ad poolUsually not offered
UMP consent inside the init flowYes, with ConsentMode and the privacy options formUsually a standalone consent call
iOS ATT ordering handled explicitlyYesVaries
Paid/revenue events and mediation response infoYesVaries
Kotlin/Native test linking on iOSSolved by a published Gradle pluginUsually unaddressed
iOS distribution modelBindings-only cinterop; the app links GMA via SPMVaries

The closest alternative in this space is LexiLabs-App/basic-ads. It covers banner, interstitial and rewarded formats. If those three formats are all a project needs and its API shape suits the codebase better, it is a reasonable choice. AdMob CMP exists for projects that also need native ads, app-open ads, consent wired into initialization, or Kotlin/Native tests that link on iOS.

Which platforms and versions does it run on?

Section titled “Which platforms and versions does it run on?”
Ad formats
FormatAndroid — GMA Next-Gen, API 26+iOS — GMA 13.x, iOS 15+Notes
Banner Supported SupportedAnchored adaptive, inline adaptive, fixed and fluid; collapsible on both platforms
Interstitial Supported SupportedTTL'd FIFO cache, 1 hour
Rewarded Supported SupportedRewardEarned event
Rewarded interstitial Supported SupportedSame controller shape as Rewarded
App-open Supported SupportedAppOpenAdCoordinator, cooldowns, blocking; cache TTL 4 hours
Native Supported SupportedadLayout DSL and pooling on both; iOS renders through UIKit
Cross-cutting capabilities
CapabilityAndroidiOSNotes
UMP consent and privacy-options form Supported SupportedcanRequestAds gates every request on both platforms
App Tracking Transparency Not applicable SupportedAndroid has no ATT: adManager.tracking always reports AdTrackingAuthorization.NotApplicable, and the AD_ID manifest permission governs the advertising id there instead
Mediation adapters Supported SupportedNo adapters are bundled; add the ones you need
Paid and revenue events Supported SupportedAdEvent.Paid carries AdValue and response info
Native video events Not available Supported — five eventsiOS emits VideoStarted, VideoPlayed, VideoPaused, VideoEnded and VideoMuted through GADVideoControllerDelegate. The Android GMA Next-Gen SDK exposes no equivalent callback surface on NativeAd, so Android emits none. This is an upstream SDK gap, not an admob-cmp omission — do not rely on native video events for cross-platform logic.
immersiveMode, customClickGesture, publisherProvidedId, categoryExclusions, skipUninitializedAdapters Supported Silently ignoredAndroid-only request options
customTargeting, placementId Supported SupportedMapped on both platforms; placementId maps to placementID on iOS
Platform support matrix: six formats on Android and iOSCLAUDE.md invariants #11Read this diagram in words

Android is API 26 or above, with the GMA Next-Gen SDK and UMP arriving as transitive Maven dependencies. iOS is 15.0 or above, with the GMA and UMP frameworks linked by the app through Swift Package Manager. The full version table — Kotlin, Compose Multiplatform, Android minSdk, iOS deployment target, and the GMA / UMP versions each release was built against — is on Compatibility.

A short list, because the omissions are features:

  • It ships zero mediation adapters by design. Adapters are platform binaries that must match the GMA SDK version and the publisher’s network contracts; bundling them would pin GMA versions for every consumer. Add them to your Android module and Xcode project directly. See Mediation.
  • It never embeds Google’s binaries on iOS, only cinterop bindings. The app links the real GMA and UMP frameworks itself. See Architecture for the rationale.
  • It is consumable from KMP and Gradle projects only, so a pure-Swift iOS app cannot adopt it without a Kotlin Multiplatform shim.
  • It does not support ad networks other than AdMob directly. Mediation covers that.
Section titled “Is AdMob CMP a consent management platform?”

No. In ad-tech, CMP normally means Consent Management Platform — the vendor that brokers GDPR / TCF consent. This library’s name predates that association in the project’s own history and is retained for continuity with the published Maven coordinate dev.avinya.ads:admob-cmp. It integrates Google’s User Messaging Platform to gather GDPR / TCF consent, but it is not itself a CMP vendor. The consent flow is documented on UMP consent.