I am trying to implement a basic Native ad from adMob to fit the design of my app in Jetpack Compose, but I don't understand how the onClick is supposed to be handled.
I know that it is possible using Viewbinding, but there has to be a Jetpack Compose way as well, as this is supposed to be the future of android development.
My full composable:
object Advertising {
var currentImageAd: NativeAd? by mutableStateOf(null)
}
@Composable
fun ImageAd(modifier: Modifier = Modifier, adId: String, nativeAd: NativeAd?, setNativeAd: (NativeAd?) -> Unit) {
val context = LocalContext.current
var iconPainter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(nativeAd?.icon)
.size(coil.size.Size.ORIGINAL)
.build()
)
DisposableEffect(Unit) {
val adLoader = AdLoader.Builder(context, adId)
.forNativeAd { ad ->
if (nativeAd == null) {
setNativeAd(ad)
}
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
}
})
.withNativeAdOptions(NativeAdOptions.Builder().build())
.build()
adLoader.loadAd(AdRequest.Builder().build())
onDispose {
}
}
nativeAd?.let { adData ->
Box.SimpleBox(modifier,
icon = R.drawable.advertisement,
iconColor = colors.ad,
headline = adData.headline,
body = adData.body
) {
Row(modifier = Modifier.height(IntrinsicSize.Max), horizontalArrangement = Arrangement.spacedBy(Padding.ContentSeparation.standard)) {
Image(painter = iconPainter,
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.aspectRatio(1f)
)
Button_Basic(shape = Shapes.tight,
color = colors.ad,
text = adData.callToAction ?:"Mehr",
icon = R.drawable.web_exit,
onClick = {
//What to call here?
})
}
}
}
}
Box.SimpleBox
is just a Box Container with rounded corners and Button_Basic
a clickable Box with text. The Advertisements headline, body and callToAction text all all loaded and displayed correctly, but I do not understand how you are supposed to get the onClick to work.
It seems like the AdMob Sdk handles onClick for traditional views in Xml by using the ads assets automatically, but it don't know how to get it to work in Compose. Also it seems weird to not be able to create a custom Button for a Native ad which is supposed to be customisable.
Is this just not possible in Compose yet and I need to write the ad code in XML or how do I have to approach this?
Yes it is possible.
Use below code to implement Native Ad using Jetpack Compose.
Make a remember of NativeAd object for compose function or screen
var nativeAd by remember { mutableStateOf<NativeAd?>(null) }
Also intialiaze NativeAd object using LaunchEffect as shown below
LaunchedEffect(null) {
loadNativeAd(context, context.resources.getString(R.string.native_ad_id)) {
nativeAd = it
}
}
fun loadNativeAd(context: Context, adUnitId: String, callback: (NativeAd?) -> Unit) {
val builder = AdLoader.Builder(context, adUnitId)
.forNativeAd { nativeAd ->
callback(nativeAd)
}
val adLoader = builder
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(adError: LoadAdError) {
callback(null)
}
})
.withNativeAdOptions(NativeAdOptions.Builder().build())
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
Use below functions to fetch the UI and load the Ad
@Composable
fun CallNativeAd(nativeAd: NativeAd) {
NativeAdView(ad = nativeAd) { ad, view ->
LoadAdContent(ad, view)
}
}
@Composable
fun NativeAdView(
ad: NativeAd,
adContent: @Composable (ad: NativeAd, contentView: View) -> Unit,
) {
val contentViewId by remember { mutableIntStateOf(View.generateViewId()) }
val adViewId by remember { mutableIntStateOf(View.generateViewId()) }
AndroidView(
factory = { context ->
val contentView = ComposeView(context).apply {
id = contentViewId
}
NativeAdView(context).apply {
id = adViewId
addView(contentView)
}
},
update = { view ->
val adView = view.findViewById<NativeAdView>(adViewId)
val contentView = view.findViewById<ComposeView>(contentViewId)
adView.setNativeAd(ad)
adView.callToActionView = contentView
contentView.setContent { adContent(ad, contentView) }
}
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun LoadAdContent(nativeAd: NativeAd?, composeView: View) {
Card(
modifier = maxWithModifier
.padding(horizontal = padding16, vertical = padding4)
.clip(CardDefaults.shape)
.combinedClickable {
composeView.performClick()
},
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) {
nativeAd?.let {
Column(
modifier = maxWithModifier
.padding(padding8)
) {
Row(
modifier = maxWithModifier,
horizontalArrangement = Arrangement.Start
) {
val icon: Drawable? = it.icon?.drawable
icon?.let { drawable ->
Image(
painter = rememberAsyncImagePainter(model = drawable),
contentDescription = "Ad"/*it.icon?.contentDescription*/,
modifier = Modifier
.size(padding40)
.padding(end = padding8),
contentScale = ContentScale.Crop
)
}
Column {
Text(
text = it.headline ?: "",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
)
Text(
text = it.body ?: "",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
)
}
}
it.callToAction?.let { cta ->
CornerButton(
modifier = maxWithModifier,
onClick = {
composeView.performClick()
},
content = {
Text(
text = cta.uppercase(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.titleMedium
)
}
)
}
}
} ?: run {
// Placeholder for loading state or error state
Text("Loading ad...")
}
}
}
Use compose coil for load Async Image (rememberAsyncImagePainter)
implementation("io.coil-kt:coil-compose:2.6.0")
Now just call the below function from your compose UI to load the Ad
if (nativeAd != null) {
CallNativeAd(nativeAd!!)
}