Implementing Google Ads in Compose? Beware of this (costly) bug!
We love Compose, and we are adapting it in our apps as much as possible. It is impressive how much it speeded up ui building in Android. There are still some libraries that have not provided Compose versions, but we can always wrap it within an AndroidView, and we are good to go, right?
That's true, but Google Ads made me such a bad joke while I was converting it to Compose, that I want to scream this out to the Android community, so that others won't be caught off guard like me.
We have been using Google’s AdManagerAdView and we were inserting them within our list of items (previously Recyclerview). While I convert the Recyclerview to LazyColumn, based on examples and tutorials I found, I have converted ads to Compose as follows:
@Composable
fun DisplayAd(
modifier: Modifier = Modifier,
adMetadata: AdMetaData,
) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
val lifecycleState = LocalLifecycleOwner.current.lifecycle.observeAsState()
val context = LocalContext.current
val adView: AdManagerAdView = remember {
AdManagerAdView(context)
}
when (lifecycleState.value) {
Lifecycle.Event.ON_PAUSE -> {
adView.pause()
}
Lifecycle.Event.ON_RESUME -> {
adView.resume()
}
Lifecycle.Event.ON_DESTROY -> {
adView.destroy()
}
else -> {}
}
AndroidView(
factory = { context ->
val builder = AdManagerAdRequest.Builder()
builder.setContentUrl(adMetadata.contentUrl)
// Add extra targeting params etc..
}
adView.apply {
setAdSizes(*adMetadata.adSizes)
adUnitId = adMetadata.adUnitId
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
loadAd(builder.build())
}
)
AdFreeLink()
}
}
And it was working fine! Ads were successfully loading. I didn't suspect anything.
However, soon after the release, ad team reported a decrease in impressions. When we debugged we found out that impressions were not firing right away or were not firing at all. They were either fired later after a scroll up and down, or while leaving the page, or some were simply lost.
After a lot of googling, I found a relevant discussion in Kotlin official slack about Google's NativeAdViews in Compose. (Credits to Oleksandr Khomenko for reporting the issue and finding a workaround) Suggested workaround was to call layout on the view after a delay. After experimenting with this, I figured out that it was the ad load that we were waiting. So calling layout on the view once the ad is loaded was solving the issue.
Here is the version with the workaround (and logs) added:
@Composable
fun DisplayAd(
modifier: Modifier = Modifier,
adMetadata: AdMetaData,
) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
val lifecycleState = LocalLifecycleOwner.current.lifecycle.observeAsState()
val context = LocalContext.current
val adView: AdManagerAdView = remember {
AdManagerAdView(context)
}
when (lifecycleState.value) {
Lifecycle.Event.ON_PAUSE -> {
adView.pause()
}
Lifecycle.Event.ON_RESUME -> {
adView.resume()
}
Lifecycle.Event.ON_DESTROY -> {
adView.destroy()
}
else -> {}
}
AndroidView(
factory = { context ->
val builder = AdManagerAdRequest.Builder()
builder.setContentUrl(adMetadata.contentUrl)
// Add extra targeting params etc..
}
adView.apply {
setAdSizes(*adMetadata.adSizes)
adUnitId = adMetadata.adUnitId
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
adListener = object : AdListener() {
override fun onAdLoaded() {
super.onAdLoaded()
Timber.d("onAdLoaded called")
// This is a workaround, to make impressions count properly.
rootView.requestLayout()
}
override fun onAdImpression() {
super.onAdImpression()
Timber.d("onAdImpression called")
}
override fun onAdFailedToLoad(adError: LoadAdError) {
super.onAdFailedToLoad(adError)
Timber.e("Ad loading failed. Message: ${adError.message}, cause = ${adError.cause}, response info = ${adError.responseInfo}")
}
}
loadAd(builder.build())
}
)
AdFreeLink()
}
}
In the end, the solution was not difficult, but it was a very sneaky issue that was not easy to spot. And as it can cost money to the company, it was a lot of stress to fix it as quickly as possible.
So please keep this in mind if you will be implementing Google ads in Compose and double check if your impressions are counted correctly.
Thanks for reading and happy coding!