Skip to content

Instantly share code, notes, and snippets.

@trinadhthatakula
Last active August 7, 2025 10:53
Show Gist options
  • Save trinadhthatakula/e4db4a7219f6ef6d066e109015edc44e to your computer and use it in GitHub Desktop.
Save trinadhthatakula/e4db4a7219f6ef6d066e109015edc44e to your computer and use it in GitHub Desktop.
A collection of reusable Jetpack Compose functions for implementing various Google AdMob ad formats in modern Android apps using Kotlin.

Implementing AdMob Ads in Jetpack Compose

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.

Table of Contents

  1. Prerequisites
  2. Dependencies
  3. Banner, Interstitial, and Other Ad Types
  4. Native Ads
  5. Resource IDs for Native Ads
  6. Usage in your App
  7. Important Considerations

Prerequisites

  • 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>

Dependencies

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")
}

Banner, Interstitial, and Other Ad Types

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

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
    }
}

Resource IDs for Native Ads

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>

Usage in your App

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")
            }
        }
    }
}

Important Considerations

  • 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.
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.LocalConfiguration
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 androidx.core.util.TypedValueCompat.dpToPx
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.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
@Composable
fun Dp.dpToPx() = with(LocalDensity.current) { [email protected]() }
@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 - if (margin.value > 5) (margin.value-5).dp.dpToPx().toInt() else 0
)
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(),
callback: InterstitialAdLoadCallback? = null
) {
InterstitialAd.load(
this,
getString(unitIdRes),
adRequest,
callback ?: object : InterstitialAdLoadCallback() {
override fun onAdLoaded(p0: InterstitialAd) {
super.onAdLoaded(p0)
p0.show(this@loadFullScreenAd)
}
}
)
}
<?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>
package com.your.package.name.presentation.admob
import android.R.attr.padding
import android.graphics.Color.toArgb
import android.graphics.Typeface
import android.util.Log
import android.util.TypedValue
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.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.Color as ComposeColor // Alias to avoid confusion
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 androidx.core.view.marginStart
import androidx.core.view.marginTop
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.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.your.package.name.R
import com.google.android.material.R as MaterialR
@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) } // For displaying errors
val density = LocalDensity.current
LaunchedEffect(adUnitId) {
Log.d("NativeAdComposable", "LaunchedEffect: Loading ad for $adUnitId")
val adLoader = AdLoader.Builder(context, adUnitId)
.forNativeAd { ad: NativeAd ->
nativeAdState?.destroy() // Destroy previous ad if any
nativeAdState = ad
adErrorState = null
Log.d(
"NativeAdComposable",
"Ad loaded. Headline: '${ad.headline}', Body: '${ad.body}'"
)
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
nativeAdState?.destroy()
nativeAdState = null
adErrorState =
"Failed to load ad: ${loadAdError.message} (Code: ${loadAdError.code})"
Log.e("NativeAdComposable", "Native ad failed to load: ${loadAdError.message}")
}
override fun onAdClicked() {
super.onAdClicked()
Log.d("NativeAdComposable", "Ad clicked")
}
override fun onAdImpression() {
super.onAdImpression()
Log.d("NativeAdComposable", "Ad impression")
}
})
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
DisposableEffect(Unit) {
onDispose {
Log.d("NativeAdComposable", "DisposableEffect: Destroying ad: ${nativeAdState != null}")
nativeAdState?.destroy()
nativeAdState = null
}
}
if (adErrorState != null) {
// Optionally display an error message in your UI if the ad fails to load
// Text(adErrorState!!, color = ComposeColor.Red, modifier = modifier)
Log.e("NativeAdComposable", "Not rendering AdView due to error: $adErrorState")
}
val backgroundColor = MaterialTheme.colorScheme.background
val onBackgroundColor = MaterialTheme.colorScheme.onBackground
nativeAdState?.let { ad ->
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { ctx ->
Log.d("NativeAdComposable", "Factory: Creating views")
val nativeAdView = NativeAdView(ctx)
val headlineTextView = TextView(ctx).apply {
id = R.id.ad_headline // Assign an ID (optional but good practice)
textSize = 18f
setTypeface(null, Typeface.BOLD)
setTextColor(onBackgroundColor.toArgb())
val padding = with(density) { 5.dp.toPx().toInt() }
setPadding(
padding,
padding,
padding,
padding
)
Log.d("NativeAdViewFactory", "Headline TextView created")
}
val mediaView = MediaView(ctx).apply {
id = R.id.ad_media // Assign an ID
setImageScaleType(ImageView.ScaleType.CENTER_CROP)
Log.d("NativeAdViewFactory", "MediaView created")
}
val bodyTextView = TextView(ctx).apply {
id = R.id.ad_body // Assign an ID
textSize = 14f
setTextColor(onBackgroundColor.toArgb())
val padding = with(density) { 5.dp.toPx().toInt() }
setPadding(
padding,
padding,
padding,
padding
)
Log.d("NativeAdViewFactory", "Body TextView created")
}
val callToActionButton = Button(ctx).apply {
id = R.id.ad_call_to_action // Assign an ID
textSize = 16f
setBackgroundResource(R.drawable.btn_bg)
setTextColor(backgroundColor.toArgb())
Log.d("NativeAdViewFactory", "CTA Button created")
}
val advertiserTextView = TextView(ctx).apply {
// Style it appropriately
textSize = 12f
maxLines = 1
setTextColor(Color.Black.toArgb())
setBackgroundResource(R.drawable.native_ad_background)
val padding = with(density) { 5.dp.toPx().toInt() }
setPadding(
padding,
padding,
padding,
padding
)
Log.d("NativeAdViewFactory", "Advertiser TextView created")
}
val adIconImageView = ImageView(ctx).apply {
id = R.id.ad_icon // If you have ids.xml
// Basic styling, adjust as needed
layoutParams = ViewGroup.LayoutParams(
with(density) { 48.dp.toPx().toInt() },
with(density) { 48.dp.toPx().toInt() }
)
scaleType = ImageView.ScaleType.FIT_CENTER
}
val cardView = CardView(ctx).apply {
addView(
mediaView, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
)
addView(
advertiserTextView, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = Gravity.TOP or Gravity.END
marginEnd = with(density) { 3.dp.toPx().toInt() }
topMargin = with(density) { 3.dp.toPx().toInt() }
}
)
radius = with(density) { 20.dp.toPx() }
}
val headlineRow = LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
gravity = Gravity.CENTER_VERTICAL // Align icon and headline text vertically
addView(adIconImageView, LinearLayout.LayoutParams(
with(density) { 40.dp.toPx().toInt() }, // Icon size
with(density) { 40.dp.toPx().toInt() }
).apply {
marginStart = with(density) { 8.dp.toPx().toInt() }
rightMargin = with(density) { 8.dp.toPx().toInt() }
})
addView(headlineTextView, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, // Let headline wrap
LinearLayout.LayoutParams.WRAP_CONTENT
).apply{
weight = 1f // If you want headline to take remaining space
})
}
val adContentLayout = LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
// Add views in order
addView(
cardView, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
with(density) { 0.dp.toPx().toInt() }
).apply {
val basicMargin = with(density) { 4.dp.toPx().toInt() }
topMargin = basicMargin
marginStart = basicMargin
marginEnd = basicMargin
bottomMargin = with(density) { 8.dp.toPx().toInt() }
weight = 1f
}
)
addView(
headlineRow, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = with(density) { 4.dp.toPx().toInt() } })
addView(
bodyTextView, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = with(density) { 8.dp.toPx().toInt() } })
addView(
callToActionButton, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
val vertical = with(density) { 4.dp.toPx().toInt() }
val horizontal = with(density) { 10.dp.toPx().toInt() }
topMargin = vertical
marginStart = horizontal
marginEnd = horizontal
bottomMargin = horizontal
})
Log.d("NativeAdViewFactory", "adContentLayout populated")
}
nativeAdView.addView(adContentLayout)
// IMPORTANT: Assign views to NativeAdView properties
nativeAdView.headlineView = headlineTextView
nativeAdView.mediaView = mediaView
nativeAdView.bodyView = bodyTextView
nativeAdView.callToActionView = callToActionButton
nativeAdView.advertiserView = advertiserTextView
nativeAdView.iconView = adIconImageView
// If you add other views like iconView, starRatingView, storeView, advertiserView,
// make sure to assign them here as well.
// e.g., nativeAdView.iconView = adIconImageView
Log.d(
"NativeAdComposable",
"Factory: Views created and NativeAdView properties assigned."
)
nativeAdView
},
update = { adView ->
Log.d("NativeAdComposable", "Update block: Assigning native ad to adView.")
// The NativeAd object itself handles populating the child views
// once they have been registered with setHeadlineView, setBodyView, etc.
adView.setNativeAd(ad)
// You generally DO NOT need to set text/content manually here if the views
// are correctly registered and setNativeAd is called.
// The NativeAd SDK populates these.
// However, to be absolutely sure what the ad object contains at this point:
Log.d("NativeAdComposable", "Update - Ad headline: '${ad.headline}'")
Log.d("NativeAdComposable", "Update - Ad body: '${ad.body}'")
Log.d("NativeAdComposable", "Update - Ad CTA: '${ad.callToAction}'")
(adView.headlineView as? TextView)?.let {
Log.d(
"NativeAdComposable",
"Update - HeadlineView text before SDK populates: '${it.text}'"
)
}
(adView.iconView as? ImageView)?.apply {
val icon = ad.icon?.drawable
visibility = if (icon != null) {
setImageDrawable(icon)
View.VISIBLE
} else {
View.GONE
}
}
(adView.headlineView as? TextView)?.text = ad.headline // Usually not needed
(adView.bodyView as? TextView)?.text = ad.body // Usually not needed
(adView.callToActionView as? Button)?.text = ad.callToAction // Usually not needed
(adView.advertiserView as? TextView)?.text =
if (ad.advertiser.isNullOrEmpty()) "AD" else ad.advertiser
ad.mediaContent?.let { mediaContent ->
adView.mediaView?.let { mv ->
if (mv.mediaContent != mediaContent) { // Avoid redundant calls
mv.mediaContent = mediaContent
Log.d("NativeAdComposable", "Update: Media content set.")
}
}
} ?: Log.d("NativeAdComposable", "Update: Ad has no media content.")
Log.d("NativeAdComposable", "Update block: setNativeAd(ad) called on adView.")
}
)
} ?: run {
// This block runs when nativeAdState is null (ad not loaded or error)
// You can put a placeholder Composable here if you want.
if (adErrorState == null) {
Log.d("NativeAdComposable", "nativeAdState is null, ad still loading or not requested.")
// Optional: Show a shimmer or loading indicator
// Box(modifier = modifier.fillMaxWidth().height(250.dp).background(ComposeColor.LightGray)) {
// CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
// }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment