Skip to content
Menu
Mobile

Adding AdMob Ads to an Android App

Gradle, manifest, initialization and Kotlin code for banner, interstitial and rewarded ads, plus test ads, consent and the errors you will hit.

12 min read · Updated

To add AdMob ads to an Android app you need four things: the play-services-ads dependency in Gradle, your AdMob App ID in the manifest, one call to MobileAds.initialize(), and an ad unit ID for each format you want to show. Use Google's test IDs while you build. Swap in your own IDs only when you are ready to publish.

That is the whole skeleton. The remaining work is deciding where ads appear, loading them before you need them, and handling the cases where nothing comes back. This guide goes through each step in order, with short Kotlin snippets for a banner, an interstitial and a rewarded ad, and then covers consent and the errors people hit most often.

Every dependency, class name and test ID below was checked against Google's current Android documentation at the time of writing. SDK versions move quickly, so treat the version numbers as a snapshot and confirm the latest on the Android quick start before you paste anything.

Before you write any code

You need an AdMob account and your app registered inside it. Registering gives you an App ID, which looks like ca-app-pub-1234567890123456~1234567890 (a tilde in the middle). Each ad placement then gets its own ad unit ID, which uses a slash instead: ca-app-pub-1234567890123456/1234567890. Mixing the two up is the single most common setup mistake, and it produces confusing errors, so it is worth learning the difference now. The App ID goes in the manifest. Ad unit IDs go in your code.

If you have not set up payments yet, do that early, because the first payout is gated by identity and address checks that take time. Our AdMob payment guide covers the threshold, the PIN and the timing.

Your project needs a minimum SDK of 24 and a compile SDK of 35 or higher. Those are the current requirements on Google's quick start page for the latest SDK release, and the release notes confirm that version 25.5.0 raised the minimum Android API level to 24. If your app still supports older devices, you will have to raise minSdk before you can take the current SDK.

One more thing to know up front. Google now labels the Google Mobile Ads SDK as being in maintenance mode and points new work towards its GMA Next-Gen SDK, which has its own quick start and a migration guide. The older SDK is still shipped and still receives releases, and it is the one most existing apps and mediation setups run on, so this tutorial uses it. If you are starting a brand-new project and have no mediation adapters to worry about, read the Next-Gen quick start before you choose. The ideas below (initialize once, load ahead, handle failures, get consent first) carry over; the class names and packages do not.

Setting up the project

Step 1: add the dependency

Open your Gradle settings file and make sure google() and mavenCentral() are both listed under pluginManagement and dependencyResolutionManagement. New projects from Android Studio usually have both already. Then add the library to the module-level build file:

dependencies {
    implementation("com.google.android.gms:play-services-ads:25.5.0")
}

Press Sync Now. If the sync fails with an unresolved dependency, the cause is almost always a missing repository, not a typo in the version.

You do not need to add the AD_ID permission yourself on any current version. The SDK declares it automatically from version 20.4.0 onwards. Only if you are stuck on 20.3.0 or lower and targeting Android 13 or newer would you add com.google.android.gms.permission.AD_ID by hand.

Step 2: put the App ID in the manifest

Inside the application element of AndroidManifest.xml, add a meta-data tag with the exact name com.google.android.gms.ads.APPLICATION_ID:

<manifest>
    <application>
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713" />
    </application>
</manifest>

That value is Google's published sample App ID for Android. It works for development and nothing else. When you register your real app in AdMob, replace it with your own ID.

If you leave this tag out, or format the value wrong, the app crashes at launch with a message that includes "Missing application ID". It is a blunt failure, which is useful: you will see it the first time you run the app and fix it in a minute. The same crash shows up later if a release build variant, a flavor or a manifest merge drops the tag, so check the merged manifest in Android Studio if it appears only in one build type.

Step 3: initialize the SDK once

Call MobileAds.initialize() one time, as early as is sensible. Google recommends doing it on a background thread, because initialization can do enough work to cause an "application not responding" warning on a slow device. The documented pattern uses a coroutine on the IO dispatcher:

CoroutineScope(Dispatchers.IO).launch {
    // Initialize the Google Mobile Ads SDK on a background thread.
    MobileAds.initialize(this@MainActivity) {}
}

The empty lambda is the completion listener. It runs when the SDK and any mediation adapters have finished starting, or after a 30-second timeout. If you only use AdMob itself, you can start loading ads without waiting for it. If you use mediation, wait for the listener first, otherwise the adapters may not be ready and some networks will miss the first request. The mediation guide goes into that.

Do not call initialize every time an activity opens. Once per process is enough, and the best home for it is your Application class or the first activity the user sees.

One caveat about order. If your audience includes people in the European Economic Area, the United Kingdom or Switzerland, you must collect consent before the SDK requests ads, and any consent-related request flags have to be set before initialization. The consent section further down shows how to wire that in.

Step 4: test ads and test devices

Never develop against your live ad unit IDs. Clicking your own ads can get an account flagged for invalid activity, and Google says as much in its test ads documentation. Use the demo ad units instead:

FormatAndroid demo ad unit ID
Anchored adaptive bannerca-app-pub-3940256099942544/9214589741
Interstitialca-app-pub-3940256099942544/1033173712
Rewardedca-app-pub-3940256099942544/5224354917
Rewarded interstitialca-app-pub-3940256099942544/5354046379
App openca-app-pub-3940256099942544/9257395921
Nativeca-app-pub-3940256099942544/2247696110

Emulators are test devices automatically. For a physical phone, run the app, make an ad request, and look in Logcat for an I/Ads line that tells you to call setTestDeviceIds with a long device ID. Copy that ID into your code:

val testDeviceIds = listOf("TEST_DEVICE_ID")
val configuration = RequestConfiguration.Builder()
    .setTestDeviceIds(testDeviceIds)
    .build()
MobileAds.setRequestConfiguration(configuration)

Do this before your first ad request. Test ads from Google carry a "Test Ad" label. Mediated ads from other networks do not, so if you add partners later, enable test mode inside each network's own dashboard and do not click anything you cannot identify as a test.

A new test device can take around 15 minutes, and occasionally up to a day, before it starts receiving test ads. Delete this registration code before you ship. Test traffic does not appear in your reports, which means it also earns nothing, so a build that still has demo IDs in it is a build that is quietly giving away its ad slots.

The AdMob revenue calculator is a better place to estimate income than your test builds. Test impressions tell you nothing about eCPM.

Step 5: a banner

Banners are the simplest format and the lowest paying. In our benchmark estimates a Tier 1 AdMob banner sits around $0.90 eCPM, against roughly $8.00 for an interstitial and $12.00 for rewarded video, which is why the banner format page treats them as a supplement rather than a main income source. They still make sense on screens where a full-screen ad would be annoying.

Put an empty container in your layout where the banner should sit:

<FrameLayout
    android:id="@+id/ad_view_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true" />

Then create the AdView in code, give it an ad unit ID and an adaptive size, and load it:

val adView = AdView(this)
adView.adUnitId = "ca-app-pub-3940256099942544/9214589741"
adView.setAdSize(AdSize.getLargeAnchoredAdaptiveBannerAdSize(this, 360))
binding.adViewContainer.removeAllViews()
binding.adViewContainer.addView(adView)
adView.loadAd(AdRequest.Builder().build())

The 360 is the width in dp; work it out from the real container width instead of hard-coding it. Adaptive banners pick the best height for that width, which beats the old fixed 320x50 size on most screens. Call loadAd on the main thread.

Attach an AdListener before loading if you want to log onAdLoaded and onAdFailedToLoad. When the screen goes away, remove the view from its parent and call adView.destroy(), then clear your reference. Leaving it alive leaks the activity. Banner activities also need hardware acceleration on, which is the default unless you turned it off.

If refresh is switched on for the ad unit in the AdMob console, the SDK requests new banners itself. Do not add your own timer on top.

Step 6: an interstitial

An interstitial is a full-screen ad shown at a natural break: between levels, after a task, when a screen closes. Load it early and show it later. An interstitial ad object is single use, and unused ones expire after an hour, so the pattern is load, hold, show once, then load the next.

private var interstitialAd: InterstitialAd? = null

private fun loadInterstitial() {
    InterstitialAd.load(
        this,
        "ca-app-pub-3940256099942544/1033173712",
        AdRequest.Builder().build(),
        object : InterstitialAdLoadCallback() {
            override fun onAdLoaded(ad: InterstitialAd) { interstitialAd = ad }
            override fun onAdFailedToLoad(error: LoadAdError) { interstitialAd = null }
        }
    )
}

To show it, attach a FullScreenContentCallback and clear the reference in both the dismissed and failed-to-show methods:

interstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
    override fun onAdDismissedFullScreenContent() {
        interstitialAd = null
        loadInterstitial()
    }
    override fun onAdFailedToShowFullScreenContent(error: AdError) {
        interstitialAd = null
    }
}
interstitialAd?.show(this)

If interstitialAd is null when the moment arrives, skip the ad and carry on with the game or screen. Never block the user waiting for one. And do not show the ad from inside the load callback, because the user did not ask for it at that moment.

Frequency is your decision, not the SDK's. Showing one after every tap will cost you users faster than it earns. Our guide on rewarded versus interstitial ads walks through pacing, and the interstitial format page has the pricing context.

Step 7: a rewarded ad

Rewarded ads pay the most in our tables, because the user chooses to watch the whole thing in exchange for something. Loading works the same way as an interstitial, using RewardedAd.load with a RewardedAdLoadCallback and the demo unit ca-app-pub-3940256099942544/5224354917. Showing differs in one respect: you pass a listener that fires when the user has earned the reward.

rewardedAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
    override fun onAdDismissedFullScreenContent() {
        rewardedAd = null
        loadRewarded()
    }
}
rewardedAd?.show(this) { reward ->
    grantCoins(reward.amount)
}

reward.amount and reward.type come from the reward you configure on the ad unit in AdMob. Grant the item inside the earned-reward listener, not when the ad closes. For Google-served ads the reward callback fires before the dismissed callback, but with mediation the order depends on the partner network, so do not assume it.

If real value is at stake, such as paid currency or premium content, set up server-side verification. Create a ServerSideVerificationOptions object with a custom data string, attach it to the ad after loading and before showing, and have your server confirm the callback AdMob sends. Client-only rewards are easy to fake on a rooted phone.

The rewarded format page shows how our estimates for this format break down by region.

If any of your users are in the EEA, the UK or Switzerland, Google requires you to collect consent through a certified consent management platform, and its own User Messaging Platform (UMP) SDK is the usual choice for AdMob. The message itself is created in your AdMob account under Privacy and messaging. The SDK shows it, and Google's UMP guide for Android has the full reference. See our consent messages guide for the regulatory background and which message types to enable.

Add the dependency:

implementation("com.google.android.ump:user-messaging-platform:4.0.0")

On every launch, ask for a consent update, show the form if one is required, then check canRequestAds() before starting the ad SDK:

val consentInfo = UserMessagingPlatform.getConsentInformation(this)
consentInfo.requestConsentInfoUpdate(this, ConsentRequestParameters.Builder().build(),
    {
        UserMessagingPlatform.loadAndShowConsentFormIfRequired(this) { formError ->
            if (consentInfo.canRequestAds()) startAds()
        }
    },
    { requestError -> if (consentInfo.canRequestAds()) startAds() }
)

Here startAds() is your own function that runs the initialization from Step 3 and then loads the first ads. Guard it with a flag so it only runs once, because both paths can reach it. Note that canRequestAds() is always false until the update request has run, so never read it earlier.

You also need a way for users to change their choice later. Check privacyOptionsRequirementStatus; when it equals REQUIRED, show a "Privacy settings" button in your menu that calls UserMessagingPlatform.showPrivacyOptionsForm.

If your app is aimed at children, the rules are different and stricter. Read ads in kids apps before you integrate anything. Version 25.3.0 of the SDK deprecated the two older child-directed flags in favor of setAgeRestrictedTreatment(), so check the current request configuration page for the name your version expects.

Errors you will probably see

"Missing application ID". The manifest tag is absent, misspelled or holds an ad unit ID instead of the App ID. Look for the tilde.

No fill, error code 3. The request worked but nobody had an ad to return. For a brand-new ad unit this is normal for the first few hours, and sometimes a day or two. For test units it should not happen, so recheck the ID. Persistent no-fill in production is a fill rate question: check geography, floors and whether app-ads.txt is in place, which our Google Play app-ads.txt guide covers.

Invalid request or "Cannot determine request type. Is your ad unit id correct?" The ad unit ID is wrong, or you used an interstitial ID in a rewarded request. Ad unit IDs are tied to a format.

Network error, code 2. No connectivity, or a request that timed out. Do not retry in a tight loop. Back off, or just wait for the next natural moment to load.

Crashes on the wrong thread. Ad calls such as loadAd, load and show belong on the main thread. Only the initialization call is meant for a background thread.

An ad never shows. Check that you are not showing the same object twice, that the activity is still alive, and that you did not null the reference before showing it.

Google's ad load errors page maps the log messages to causes. Ad inspector is the tool to reach for when logs are not enough. It is an overlay that lists each request, the response and, with mediation, each network's result. It only runs on registered test devices and adds some memory use while enabled, so leave it out of release builds. Google documents it on the ad inspector page.

Going live without getting flagged

Before you publish, run through this list:

  • Replace the sample App ID in the manifest and every demo ad unit ID in code.
  • Remove the setTestDeviceIds call.
  • Confirm that the consent form appears for a user in a regulated region.
  • Add your developer website and app-ads.txt, so that buyers can verify you.
  • Release to a small track first and keep an eye on the AdMob console for the first day.

Do not click your own live ads and do not ask friends to. It looks like invalid traffic, and the penalty can be a limited or disabled account under the AdMob policies. Our guides on invalid traffic and why ad accounts get limited explain how that happens and what recovery looks like.

Common questions

Can I use my real ad unit IDs while testing?

You can, but you should not. Even with test devices registered, one slip puts real ad clicks on your account. The demo IDs cost nothing and behave the same way in the SDK.

Do I need to initialize the SDK before showing banners?

Initialization should have started, yes. For plain AdMob you can request ads straight away. With mediation, wait for the completion listener so every adapter is ready.

Why do I get no ads on a brand-new app?

New ad units and new apps often return little or nothing at first. Test ads should still appear. If those do not, the problem is in your code. If only live ads are empty, give it a day and check the account for anything that needs attention.

What about iOS?

The shape is the same but the packages, plist keys and class names differ. We cover it in adding AdMob to an iOS app, and Flutter has its own guide.

What to do next

Get one format working end to end with test IDs before you add a second. A banner on your main screen and a rewarded ad behind a clearly labeled button is a perfectly good first release.

Then think about money. Open the AdMob revenue calculator, enter your expected daily users and impressions per session, and see what a realistic month looks like; the casual app estimates give a ballpark by category. Once the app has live traffic, learn to read the numbers in How to read your AdMob report, and if eCPM looks low, work through why your eCPM may be low in order.

When one network is no longer enough, the next step is AdMob mediation, and our piece on mediation versus a single network helps you decide whether it is worth the extra SDKs.