android-jetpack-composeadviewlazycolumnandroid-jetpack-compose-list

AdManagerAdView not rendering ad image when off screen in LazyColumn


I am wrapping an AdManagerAdView in an AndroidView so I can use it in Jetpack Compose. The image fails to load when I use it in a LazyColumn AND the AdManagerAdView tries to load the image before the composable is on screen.

If I scroll quickly to that element, so LazyColumn composes it AND it is on screen before the image comes back from the ad server, it works as expected.

LazyColumn {
        items(5) {
            SomeOtherComposable(it)
        }

        item {
            AndroidView(
                modifier = Modifier
                    .width(300.dp)
                    .height(250.dp)
                    .background(Color.Green),
                factory = { context ->
                    val adView = AdManagerAdView(context)
                    adView.adSize = AdSize.MEDIUM_RECTANGLE
                    adView.adUnitId = adUnitId
                    adView.loadAd(Builder().build())
                    adView
                }
            )
        }

        items(5) {
            SomeOtherComposable(it)
        }
}

For demo purposes...

@Composable
fun SomeOtherComposable(i: Int) {
    Text(
        text = "SomeOtherComposable $i",
        modifier = Modifier
            .fillMaxWidth()
            .height(320.dp)
            .background(Color.White)
    )
}

This also works fine if the wrapped AdManagerAdView is used in a non-lazy Column or any other Compose layout.

This feels like a weird timing thing in LazyColumn that just happens to manifest when the Composable isn't on screen yet since using it in a regular Column works fine under the same scenario.

Has anyone experienced anything like this?

SOLVED See my answer below


Solution

  • Ok, the issue is actually that both factory{} and update{} are called before the AndroidView has gone through the layout pass. In my steps to reproduce, the ad image is coming back and being added to the internal view before it has a measured width and height.

    The solution is to delay that load call until after the layout pass using doOnLayout{} like so:

    AndroidView(
            modifier = Modifier
                .width(300.dp)
                .height(250.dp)
                .background(Color.Green),
            factory = { context ->
                val adView = AdManagerAdView(context)
                adView.adSize = AdSize.MEDIUM_RECTANGLE
                adView
            },
            update = { adView ->
                adView.adUnitId = adUnitId
                adView.doOnLayout {
                    adView.loadAd(Builder().build())
                }
            }
        )