Google AdMob Next Gen SDK: Implementing Inline Adaptive Banners in a Jetpack Compose lazy column 

Mobile advertising in today’s dynamic market is visible everywhere, from your favorite websites to your Android or iOS apps. This is used as a strategy to keep some content free of charge in exchange for in-app ads to the users.

Google dominates the digital advertising market, accounting for the majority of global advertising revenue. Hence, they develop an SDK for developers to integrate and display in-app ads more easily, which is the Google AdMob. It is a mobile advertising platform that helps app developer monetize their apps by displaying in-app ads. In such a manner, developers can earn some revenue from a vast network of advertisers while enabling advertisers to reach their target audience within the mobile apps.

AdMob simplifies this complex process of in-app advertising by providing developers with an AdMob SDK (Software Developer Kit). That enables them to integrate it into their applications, creating designated “ad units” where ads can be displayed. Once integrated, it leverages Google’s immense advertising ecosystem to automatically deliver relevant and high-performing ads to app users. These ads are generated and paid for by advertisers (in most cases are companies), with developers earning revenue for impressions, clicks or other user interactions with the ads.

Besides simply showing ads, AdMob also offers a suite of tools and features to optimize monetization, enhance user experience, and provide valuable insight into ad performance. The goal is to empower developers to focus on building the app while AdMob handles the task of ad serving, targeting the audience, and payment.

With AdMob any app developer can transform their passion into a profitable venture. AdMob provides a robust and accessible solution for generating sustainable income through in-app advertising.

AdMob Next Gen Ads SDK

The current Android AdMob SDK provides such feature and ability. Recently Google introduced a brand new AdMob SDK called AdMob Next Gen Ads which improves ads loading performance and the ability to pre-fetch ads for a smoother ad display experience.

Configure your app

To configure your app, add the corresponding dependency and initialize MobileAd by following the steps in the following link. For simplification reasons, we are going to use the dedicated sample AdMob app ID: 

ca-app-pub-3940256099942544~3347511713

UMP SDK implementation (Important)

Before implementing the Next Gen Ads SDK in your Android app, you must get consent from the user before proceeding. This is important for data collection, especially under regulations like GDPR (General Data Protection Regulation) in the EEA (European Economic Area) and the CCPA (California Consumer Privacy Act).

To comply with these regulations, Google introduced the UMP (User Messaging Platform) SDK for developers.    

For the full guide on how to implement it, read the following article.

Inline adaptive banner ads in a lazy column (using AdMob Next Gen Ad SDK)

In our previous article, we demonstrated how to implement an inline adaptive banner ad in a static view. What we are going to demonstrate in this article is the approach that most news apps use to show ads in the news list.

To make the process as simple as possible, we are going to use the following test adUnit:

ca-app-pub-3940256099942544/9214589741

Implementation

Such implementation is bit more complicated in comparison with the static view. In order to load ads faster we are going to preload them and store them in a temporary ad holder (ad caching in our example). The logic and flow are as following:

Lazy column banner loading flow

It starts when the composable ad view becomes visible, it will make a banner ad request through the AdFetcher. Once received, it will cache the specific bannerAd with a unique key which will be reused later when the view reappears. A use case where this becomes handy is when the user scrolls down and back up without navigating to the detail screen.

Banner ad retrieval and caching flow chart

Architectural pattern

The architectural pattern used in this example will be MVI (Model-View-Intent). Such a pattern works best with Jetpack Compose (in my experience).

MVI flow

View state data model

@Parcelize
data class ListViewState(val isLoading:Boolean = true, val items: List<ListItem> = emptyList()): Parcelable

List item data model

@Parcelize
data class ListItem(val title:String, val adUnit: NextGenAdUnit? = null): Parcelable

ViewModel

class InlineAdaptiveViewModel: ViewModel() {

    private var _inlineAdaptiveViewState = MutableStateFlow(ListViewState())

    val inlineAdaptiveViewState: StateFlow<ListViewState>
        get() = _inlineAdaptiveViewState.asStateFlow()

    init {
        CoroutineScope(Dispatchers.Main).launch {
            loadData()
        }
    }

    private suspend fun loadData() {

        MobileAdsManager.clearAllCachedBanner()
        delay(2.seconds) // Simulate remote server api call
        _inlineAdaptiveViewState.update { it.copy(isLoading = false, items = getDummyItemList()) }

    }

    private fun getDummyItemList(): List<ListItem> {

        val items = ArrayList<ListItem>()

        for (item in 1..50) {
            items.add(ListItem(title = "List item $item"))
            if (item % 10 == 0) {
                items.add(ListItem(title = "", adUnit = NextGenAdUnit.BannerAd(_key = "item_$item")))
            }
        }

        return items
    }
}

Composable lazy column (UI)

@Composable
fun InlineAdaptiveListView(
    modifier: Modifier = Modifier,
    viewState: State<ListViewState>
) {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(key1 = lifecycleOwner, effect = {

        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_ANY) {
                MobileAdsManager.clearAllCachedBanner()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    })

    Box(modifier = modifier) {

        AnimatedVisibility(
            visible = viewState.value.isLoading,
            enter = fadeIn(),
            exit = fadeOut()
        ) {
            LoadingView()
        }

        LazyColumn(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {

            items(items = viewState.value.items) {

                ListCustomCell(item = it)
            }
        }
    }
}
@Composable
private fun LoadingView() {

    Box(
        modifier = Modifier
            .fillMaxSize()
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(
                16.dp,
                alignment = Alignment.CenterVertically
            )
        ) {
            CircularProgressIndicator(modifier = Modifier.size(50.dp))
            Text("Loading data....", style = MaterialTheme.typography.bodyLarge)
        }
    }
}
@Composable
fun ListCustomCell(item: ListItem) {

    item.adUnit?.let {
        BannerAdView(bannerAdUnit = it)
    } ?: run {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(80.dp)
                .padding(all = 16.dp),
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Image(
                modifier = Modifier
                    .size(28.dp)
                    .clip(CircleShape)
                    .border(width = 1.dp, color = Color.LightGray, CircleShape),
                painter = painterResource(Drawable.droid_dev_tips_logo),
                contentDescription = null,
                contentScale = ContentScale.Crop
            )

            Text(text = item.title)
        }
    }
}

You can check the source code from our GitHub repository.