A guide to integrating Google AdMob ads (Banner, Adaptive Banner, Collapsible Banner, Interstitial, and Native Ads) into an Android application built with Jetpack Compose.
This guide provides reusable @Composable
functions to display different types of ads and follows modern Android development practices, which as a mobile developer who prefers state-based programming with MVVM, you'll find quite familiar.
- Prerequisites
- Dependencies
- Banner, Interstitial, and Other Ad Types
- Native Ads
- Resource IDs for Native Ads
- Usage in your App
- Important Considerations
- An Android project using Jetpack Compose.
- A Google AdMob account and a registered app with ad unit IDs.
- Your
AndroidManifest.xml
configured with your AdMob App ID:
<manifest>
<application>
<!-- Sample AdMob App ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="YOUR_ADMOB_APP_ID"/>
</application>
</manifest>
Add the Google Mobile Ads SDK dependency to your app's build.gradle.kts
or build.gradle
file.
// build.gradle.kts
dependencies {
implementation("com.google.android.gms:play-services-ads:23.0.0")
}
This file contains composables for various banner ad types and a helper function for loading interstitial ads.
AdMobAds.kt
package com.your.package.name.presentation.admob
import android.app.Activity
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
@Composable
fun AdMobBanner(
modifier: Modifier = Modifier,
@StringRes unitIdRes: Int,
adSize: AdSize = AdSize.FULL_BANNER
) {
val unitId = stringResource(unitIdRes)
AndroidView(
modifier = modifier.clip(RoundedCornerShape(10)),
factory = { context ->
AdView(context).apply {
setAdSize(adSize)
adUnitId = unitId
loadAd(AdRequest.Builder().build())
}
}
)
}
@Composable
fun AdMobAdaptiveBanner(
modifier: Modifier = Modifier,
margin: Dp = 0.dp,
strokeColor : Color = MaterialTheme.colorScheme.onBackground,
strokeWidth: Dp = 0.2.dp,
@StringRes unitIdRes: Int
) {
val context = LocalContext.current
val windowInfo = LocalWindowInfo.current
val screenWidth = windowInfo.containerSize.width
val adSize = AdSize.getCurrentOrientationInlineAdaptiveBannerAdSize(
context,
screenWidth
)
val unitId = stringResource(unitIdRes)
AndroidView(
modifier = modifier
.padding(margin)
.border(strokeWidth, strokeColor, RoundedCornerShape(10))
.clip(RoundedCornerShape(10)),
factory = {
AdView(context).apply {
setAdSize(adSize)
adUnitId = unitId
loadAd(AdRequest.Builder().build())
}
}
)
}
enum class CollapseDirection(val value: String) {
TOP("top"),
BOTTOM("bottom")
}
@Composable
fun AdMobCollapsableBanner(
modifier: Modifier = Modifier,
@StringRes unitIdRes: Int,
adSize: AdSize = AdSize.FULL_BANNER,
collapseDirection: CollapseDirection = CollapseDirection.TOP
) {
val unitId = stringResource(unitIdRes)
AndroidView(
modifier = modifier
.padding(5.dp)
.clip(RoundedCornerShape(10)),
factory = { context ->
AdView(context).apply {
setAdSize(adSize)
adUnitId = unitId
val extras = Bundle()
extras.putString("collapsible", collapseDirection.value)
loadAd(
AdRequest.Builder()
.addNetworkExtrasBundle(AdMobAdapter::class.java, extras)
.build()
)
}
}
)
}
fun Activity.loadFullScreenAd(
@StringRes unitIdRes: Int,
adRequest: AdRequest = AdRequest.Builder().build(),
onAdLoaded: (InterstitialAd) -> Unit,
onAdFailedToLoad: (LoadAdError) -> Unit,
onAdDismissed: () -> Unit
) {
InterstitialAd.load(
this,
getString(unitIdRes),
adRequest,
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(interstitialAd: InterstitialAd) {
super.onAdLoaded(interstitialAd)
onAdLoaded(interstitialAd)
interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
onAdDismissed()
}
}
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
super.onAdFailedToLoad(loadAdError)
onAdFailedToLoad(loadAdError)
}
}
)
}
Native ads are more complex as they require you to build the ad UI from individual components. This composable handles loading the native ad and lays out its components.
NativeAdComposable.kt
package com.your.package.name.presentation.admob
import android.graphics.Typeface
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.MediaView
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
import com.your.package.name.R // Make sure to import your R file
@Composable
fun NativeAdComposable(
modifier: Modifier = Modifier,
adUnitId: String
) {
val context = LocalContext.current
var nativeAdState by remember { mutableStateOf<NativeAd?>(null) }
var adErrorState by remember { mutableStateOf<String?>(null) }
val density = LocalDensity.current
LaunchedEffect(adUnitId) {
val adLoader = AdLoader.Builder(context, adUnitId)
.forNativeAd { ad: NativeAd ->
nativeAdState?.destroy()
nativeAdState = ad
adErrorState = null
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
nativeAdState?.destroy()
nativeAdState = null
adErrorState = "Failed to load ad: ${loadAdError.message}"
Log.e("NativeAdComposable", "Native ad failed to load: ${loadAdError.message}")
}
})
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
DisposableEffect(Unit) {
onDispose {
nativeAdState?.destroy()
nativeAdState = null
}
}
nativeAdState?.let { ad ->
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { ctx ->
val nativeAdView = NativeAdView(ctx)
// Create views for the native ad components
val headlineTextView = TextView(ctx).apply { id = R.id.ad_headline /* ... styling ... */ }
val mediaView = MediaView(ctx).apply { id = R.id.ad_media /* ... styling ... */ }
val bodyTextView = TextView(ctx).apply { id = R.id.ad_body /* ... styling ... */ }
val callToActionButton = Button(ctx).apply { id = R.id.ad_call_to_action /* ... styling ... */ }
val adIconImageView = ImageView(ctx).apply { id = R.id.ad_icon /* ... styling ... */ }
// Create a layout to hold the ad views
val adContentLayout = LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
// ... add all your views to this layout ...
}
nativeAdView.addView(adContentLayout)
// Register views with the NativeAdView
nativeAdView.headlineView = headlineTextView
nativeAdView.mediaView = mediaView
nativeAdView.bodyView = bodyTextView
nativeAdView.callToActionView = callToActionButton
nativeAdView.iconView = adIconImageView
nativeAdView
},
update = { adView ->
adView.setNativeAd(ad)
(adView.headlineView as? TextView)?.text = ad.headline
(adView.bodyView as? TextView)?.text = ad.body
(adView.callToActionView as? Button)?.text = ad.callToAction
ad.icon?.drawable?.let {
(adView.iconView as? ImageView)?.setImageDrawable(it)
adView.iconView?.visibility = View.VISIBLE
} ?: run {
adView.iconView?.visibility = View.GONE
}
ad.mediaContent?.let { adView.mediaView?.setMediaContent(it) }
}
)
} ?: run {
// Optional: Show a placeholder or loading indicator
}
}
For the NativeAdComposable
to correctly identify and populate the ad's views (headline, image, etc.), you must define specific IDs in your project's resources.
Create a file named ids.xml
in your app/src/main/res/values/
directory and add the following content:
res/values/ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="ad_headline" />
<item type="id" name="ad_body" />
<item type="id" name="ad_call_to_action" />
<item type="id" name="ad_media" />
<item type="id" name="ad_icon" />
</resources>
Here's how you can use these composables in your screens.
import android.app.Activity
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.your.package.name.R // Make sure to import your R file
@Composable
fun AdScreen() {
val context = LocalContext.current as Activity
var interstitialAd by remember { mutableStateOf<InterstitialAd?>(null) }
// Load an interstitial ad
LaunchedEffect(Unit) {
context.loadFullScreenAd(
unitIdRes = R.string.your_interstitial_ad_unit_id,
onAdLoaded = { ad ->
interstitialAd = ad
},
onAdFailedToLoad = {
// Handle error
},
onAdDismissed = {
interstitialAd = null // Ad is used, invalidate it
}
)
}
Scaffold { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Adaptive Banner
AdMobAdaptiveBanner(unitIdRes = R.string.your_banner_ad_unit_id)
Spacer(Modifier.height(20.dp))
// Native Ad
NativeAdComposable(adUnitId = stringResource(R.string.your_native_ad_unit_id))
Spacer(Modifier.height(20.dp))
// Button to show Interstitial Ad
Button(
onClick = { interstitialAd?.show(context) },
enabled = interstitialAd != null
) {
Text("Show Interstitial Ad")
}
}
}
}
- Test Ads: Always use test ad unit IDs during development to avoid being flagged for invalid traffic.
- Ad-Loading Logic: Be mindful of when and where you load ads. Loading a new interstitial ad should happen after the previous one is dismissed or fails to load.
- User Experience: Don't overload your UI with ads. Place them thoughtfully. For interstitial ads, show them at natural transition points in your app's flow (e.g., after completing a level in a game).
- Error Handling: The provided code includes basic error logging. Enhance this to handle ad-loading failures gracefully, perhaps by hiding the ad container or showing alternative content.