Quickstart
What will you have at the end?
Section titled “What will you have at the end?”Five minutes from now, an Android or iOS app that renders a real banner ad inside a Compose Multiplatform screen, using Google’s public sample ad units. No AdMob account, no ad unit of your own, no App Store / Play Store ceremony. The integration is exactly the integration you will ship — only the test ids will be replaced with your own before release.
The five minutes assume you already have a working Kotlin Multiplatform
project with a Compose Multiplatform screen in commonMain. If you do not,
start from the KMP project wizard in your
IDE; the only deviation from the default is adding the dependency line in
the next step.
How do I add the dependency?
Section titled “How do I add the dependency?”The quickstart deliberately uses Google’s sample ad units so no AdMob account is needed. The full installation story — version catalogs, the Gradle plugin, Kotlin/Native test linking — is on Installation.
kotlin { sourceSets { commonMain.dependencies { implementation("dev.avinya.ads:admob-cmp:1.1.0") } }}The Android GMA Next-Gen SDK and UMP arrive as transitive Maven
dependencies, so no extra Android dependency is needed. iOS deliberately
does not include the binaries — the app links them through Swift Package
Manager. That sounds like extra work for a greenfield project, and it is
the first time only: see iOS setup for the two
package URLs and the three Info.plist keys. The
architecture reference explains why the
distribution works that way.
How do I initialize AdMob in Compose Multiplatform?
Section titled “How do I initialize AdMob in Compose Multiplatform?”Initialize once at the root of your app, ideally as a side effect on first composition. The library gathers consent and starts Mobile Ads in one call:
@Composablefun App() { val adManager = rememberAdManager()
LaunchedEffect(Unit) { adManager.gatherConsentAndInitialize( AdConfig( androidAppId = TestAdIds.ANDROID_APP_ID, iosAppId = TestAdIds.IOS_APP_ID, testMode = true, ) ) }
val status by adManager.status.collectAsState() if (status is AdManagerStatus.Ready) { AdScreen() }}Each piece is doing real work. rememberAdManager() returns a process-wide
singleton — the same one AdMob.manager(context) returns on Android, so
there is never a second manager in a process. gatherConsentAndInitialize
runs the UMP consent flow, invokes any initialization hooks, then starts
Mobile Ads. And status is the gate the rest of the UI is built on:
AdManagerStatus is Idle, Initializing, ConsentRequired, Ready,
Disabled(reason), or Failed(error, retryable).
A practical note on the gate: gating the entire ad screen on Ready is
fine for the first integration, but the production app usually wants to
preload placements in the background and only block the first show. The
common shape is to keep showing app content while Initializing, and to
flip the global UI gate the first time a show() button is pressed. The
interstitial guide covers the preload-and-show
pattern in detail.
The order on iOS matters. UMP consent, then App Tracking Transparency, then the first ad request — that is the only sequence that preserves the IDFA for personalised ads. App Tracking Transparency explains why requesting ads before ATT resolves permanently forfeits the IDFA for those requests.
How do I show my first banner?
Section titled “How do I show my first banner?”val placement = remember { AdPlacement( id = "home_banner", format = AdFormat.Banner, androidAdUnitId = TestAdIds.ANDROID_BANNER, iosAdUnitId = TestAdIds.IOS_BANNER, strictTestMode = true, )}
BannerAdView( placement = placement, modifier = Modifier.fillMaxWidth(), onEvent = { event -> when (event) { is AdEvent.Loaded -> println("banner loaded") is AdEvent.LoadFailed -> println("banner failed: ${event.error}") else -> Unit } },)BannerAdView measures its own container, supplies the width, loads,
attaches, refreshes and disposes the platform banner view for you. Setting
strictTestMode = true throws at construction if the placement points at
a production ad unit, which is exactly what you want in a debug build — a
test build can never request live ads. The remember around the placement
matters because controllers are placement-keyed and cached for the
manager’s lifetime, so ids must be static and finite.
The onEvent callback exposes the full AdEvent sealed hierarchy: Loaded,
LoadFailed, Impression, Clicked, OpenedFullScreen, ClosedFullScreen
and Paid. Most apps only log in development. Production code usually
hoists ad events up to a single analytics collector — see
Revenue and paid events for the paid-event
shape and the cross-platform gotchas.
Why is nothing rendering yet?
Section titled “Why is nothing rendering yet?”A short triage, each item the one thing that is usually wrong:
- The manager is not
AdManagerStatus.Readyyet. Gate the UI onstatusbefore placing anyBannerAdViewor callingload(). - Consent has not been granted, so loads fail with
AdErrorCode.CONSENT_REQUIRED. RungatherConsentAndInitializeand checkadManager.consent.canRequestAds. - The Android manifest is missing
com.google.android.gms.ads.APPLICATION_ID. GMA crashes at startup if it is missing. See Android setup. - The iOS app has not added the two Swift Package Manager packages. See iOS setup.
- The banner placement uses
BannerRefreshPolicy.Manualand nothing calledrefresh(). Manual policy is no auto-load; you must calladManager.banner(placement).refresh()yourself. - The UMP consent form was dismissed without a choice, so
canRequestAdsis stillfalse. The privacy-options form must be reopened and the user must accept before any request reaches the network.
When none of those explain it, the full symptom table is on Troubleshooting.
What changes before you ship?
Section titled “What changes before you ship?”Five swaps. None of them changes the structure of the integration:
- Replace both sample app ids and every sample ad-unit id with your own.
- Set
testMode = falseonAdConfig. Leaving it on means the consent debug settings still apply. - Keep
strictTestMode = truein debug builds only. - Complete the Play Data safety declaration in the Play Console.
- Add
NSUserTrackingUsageDescriptionto the iOSInfo.plist, or every iOS request will serve non-personalised ads at materially lower eCPM. See App Tracking Transparency.
The test ids are recognisable on sight: TestAdIds.ANDROID_BANNER is
ca-app-pub-3940256099942544/9211549742, which is the same id Google uses
in its own sample apps. The release-time checklist above is the simplest
way to keep a debug build from ever being mistaken for a release build
once you are past the quickstart.