AdMob Next Gen Ad Rewarded Ads: A Step-by-Step Guide for Android Developers in Jetpack Compose

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.

Rewarded ad (using AdMob Next Gen Ad SDK)

In our previous articles, we focused more on the generic everyday mobile ads that users can encounter when using an Android app. This is Inline Adaptive Banner Ads (see article 1 [link1] and article 2 [link2]) and Interstitial ads [link]. Beside these ones there another type that most game apps uses and that is the rewarded ads. Rewarded ad like that name already said reward users with in-app items for interacting with video ads, playable ads and surveys etc. These are some examples on where it can be used within an Android app. This type not only rewards the user but also the developers/owners of the app, since it generates higher revenue compared to other types.

You might be asking: Why does this generate such a high revenue? Is rewarded ad is a type of advertisement in which users voluntarily watch an ad in exchange for a reward. In a typical app or game such a reward could be:

  • Extra lives or in-game currency/Coins
  • Access to premium content
  • Unlocking premium features

Key characteristics

  • It is clear, tangible benefit for watching.
  • Since it is opt-in, users often find it less annoying than other ad formats.
  • User voluntarily chooses to watch the ad (it’s not forced).

For more example on the Do and don’t check the Google AdMob Rewarded ad playbook.

AdMob Next Gen Ads Rewarded Ad implementation in Jetpack Compose

In this example, we are going to use the Google sample ad unit:

ca-app-pub-3940256099942544/5224354917

Implementing a rewarded ad is as simple as requesting a Rewarded Ad instance through the SDK and providing a callback interface. Such an interface is just like a normal API call with onAdLoaded (this include the RewardedAd) and onAdFailedToLoad (error handling).

Demo app flow

To keep it as simple as possible, after every 5 to 10 seconds the option will appear for the user to tap which will show a rewarded ads.

Article detail screen

Be aware that the user has the option dismiss it so therefore in the callback interface credit will be added ones the rewarded ads is which fully consumed. In order to detect this, we will conduct all the appropriate actions in the onUserEarnedReward callback function. Once the user obtains the credit, they will be able to read a premium article.

NavHost
@Composable
fun RewardedAd(modifier: Modifier = Modifier) {

    val navController = rememberNavController()
    val context = LocalContext.current
    val dataStateKey = "data"

    Box(modifier = modifier.fillMaxSize()) {

        NavHost(navController = navController, startDestination = RewardedAdViewRoute.RewardedAdsArticleList.route) {

            composable(route = RewardedAdViewRoute.RewardedAdsArticleList.route) {
                val rewardedAdViewModel: RewardedAdViewModel =
                    viewModel(factory = RewardedAdViewModelFactory(repository = RewardedAdNewsRepositoryImpl(), adFetcher = MobileAdsManager))
                val viewState =
                    rewardedAdViewModel.rewardedAdViewState.collectAsStateWithLifecycle()
                RewardedAdsArticleListView(
                    viewState = viewState,
                    action = rewardedAdViewModel::performAction,
                    onItemClicked = {

                        if (viewState.value.isLoadingAd)
                            return@RewardedAdsArticleListView

                        if (it.premium && viewState.value.credit == 0) {
                            Toast.makeText(context, context.getString(AppString.no_sufficient_credit), Toast.LENGTH_LONG).show()
                            return@RewardedAdsArticleListView
                        }

                        navController.currentBackStackEntry?.savedStateHandle?.set(dataStateKey, it)
                        navController.navigateToView(RewardedAdViewRoute.RewardedAdsArticleDetail.route)

                        if (it.premium) {
                            rewardedAdViewModel.performAction(action = RewardedAdViewAction.SubtractCredit)
                        }
                    })
            }

            composable(
                route = RewardedAdViewRoute.RewardedAdsArticleDetail.route
            ) {
                val data = navController.previousBackStackEntry?.savedStateHandle?.get<RewardedAdListDisplayItem>(dataStateKey)
                ArticleDetailScreen(
                    data = data
                )
            }
        }
    }
}
Rewarded ads article list view
@Composable
fun RewardedAdsArticleListView(
    viewState: State<RewardedAdViewState>,
    action: (RewardedAdViewAction) -> Unit,
    onItemClicked: (RewardedAdListDisplayItem) -> Unit,
    modifier: Modifier = Modifier
) {

    val lazyColumnListState = rememberLazyListState()

    Box(modifier = modifier.fillMaxSize()) {

        AnimatedVisibility(
            modifier = Modifier.fillMaxSize(),
            visible = viewState.value.isLoading,
            enter = fadeIn(),
            exit = fadeOut(animationSpec = tween(300))
        ) {
            ShimmerListView()
        }

        LazyColumn(
            state = lazyColumnListState,
            modifier = Modifier
                .fillMaxSize()
                .align(Alignment.Center)
        ) {
            itemsIndexed(viewState.value.newsList) { index, listItem ->
                RewardedAdListItem(displayItem = listItem, onClick = onItemClicked)

                if (index < viewState.value.newsList.lastIndex) {
                    HorizontalDivider()
                } else {
                    Spacer(
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(50.dp)
                    )
                }
            }
        }

        AnimatedVisibility(
            modifier = Modifier.align(Alignment.BottomCenter),
            visible = !viewState.value.isLoading,
            enter = fadeIn(),
            exit = fadeOut(animationSpec = tween(300))
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(shape = RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp, bottomEnd = 0.dp, bottomStart = 0.dp), color = colorResource(id = R.color.droid_dev_tips_green).copy(alpha = 0.95f)),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically
            ) {

                Column(
                    modifier = Modifier
                        .fillMaxHeight()
                        .weight(1.0f),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {

                    if (viewState.value.showRewardedAdButton) {

                        if (viewState.value.isLoadingAd) {
                            CircularProgressIndicator(
                                modifier = Modifier.size(30.dp),
                                color = Color.White
                            )
                        } else {
                            TextButton(onClick = {
                                action(RewardedAdViewAction.LoadAndShowRewardedAd)
                            }) {
                                Text(
                                    text = stringResource(id = AppString.more_credit_message),
                                    style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
                                    modifier = Modifier
                                        .pulseEffect(),
                                    color = Color.White
                                )
                            }
                        }

                    }
                }

                Column(
                    modifier = Modifier.size(50.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {

                    Text("Credit", style = MaterialTheme.typography.labelSmall, color = Color.White, modifier = Modifier.padding(top = 3.dp))

                    Box(modifier = Modifier.size(35.dp)) {
                        Text(
                            "${viewState.value.credit}",
                            color = Color.White,
                            fontWeight = FontWeight.Bold,
                            modifier = Modifier.align(Alignment.Center)
                        )
                    }
                }
            }
        }
    }

    if (viewState.value.showRewardedAd) {
        viewState.value.rewardedAdView?.let {
            RewardedAdView(rewardedAd = it, onAdDismissed = {
                action(RewardedAdViewAction.DismissRewardedAd)
            }, onUserRewardCollected = {
                action(RewardedAdViewAction.OnRewardedAdCompleted)
            })
        } ?: run {
            action(RewardedAdViewAction.DismissRewardedAd)
        }
    }
}
Rewarded Ad View
@Composable
fun RewardedAdView(
    rewardedAd: RewardedAd?,
    modifier: Modifier = Modifier,
    onAdDismissed: () -> Unit,
    onUserRewardCollected: () -> Unit
) {

    if (rewardedAd == null) {
        FailedToLoadPlaceholder(modifier = modifier)
        onAdDismissed()
        return
    }

    LocalActivity.current?.let { _activity ->

        rewardedAd.adEventCallback = object : RewardedAdEventCallback {

            override fun onAdShowedFullScreenContent() {
                super.onAdShowedFullScreenContent()
                println("[Rewarded Ad] - Ad Showed FullScreen Content")
            }

            override fun onAdDismissedFullScreenContent() {
                super.onAdDismissedFullScreenContent()
                println("[Rewarded Ad] - On Ad Dismissed FullScreen Content")
                onAdDismissed()
            }

            override fun onAdFailedToShowFullScreenContent(fullScreenContentError: FullScreenContentError) {
                super.onAdFailedToShowFullScreenContent(fullScreenContentError)
                println("[Rewarded Ad] - On Ad Failed to show FullScreen, cause: ${fullScreenContentError.message}")
            }

            override fun onAdImpression() {
                super.onAdImpression()
                println("[Rewarded Ad] - On Ad Impression")
            }

            override fun onAdClicked() {
                super.onAdClicked()
                println("[Rewarded Ad] - On Ad Clicked")
            }
        }

        rewardedAd.show(_activity, object : OnUserEarnedRewardListener {

            override fun onUserEarnedReward(reward: RewardItem) {
                println("[Rewarded Ad] - On User Earned Reward")
                println("[Rewarded Ad] - type: ${reward.type}")
                println("[Rewarded Ad] - amount: ${reward.amount}")
                onUserRewardCollected()
            }
        })
    } ?: run {
        FailedToLoadPlaceholder(modifier = modifier)
        onAdDismissed()
    }
}

@Composable
private fun FailedToLoadPlaceholder(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .fillMaxWidth()
            .height(400.dp)
            .background(color = Color.LightGray)
    ) {
        Text(
            text = stringResource(id = AppString.failed_to_load_rewarded_ad),
            fontWeight = FontWeight.Bold,
            modifier = Modifier.align(
                Alignment.Center
            )
        )
    }
}

Rewarded ads are a win-win situation for both users and developers. Users gain access to valuable content or features at no cost simply by choosing to engage with an ad. Such an opt-in approach enhances user satisfaction and retention (when implemented well), while helping developers monetize their apps in a non-intrusive, user-friendly way.

See our GitHub repository for the full example code.