Skip to content

Commit

Permalink
Added Jetpack Compose Sample LazyList Banner.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 654936228
  • Loading branch information
nventimigli authored and copybara-github committed Aug 27, 2024
1 parent 04defc4 commit 922b3f4
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private fun loadAdaptiveBannerAd(context: Context, width: Int, isPreviewMode: Bo
}

override fun onAdFailedToLoad(error: LoadAdError) {
Log.e(TAG, "Banner ad failed to load.")
Log.e(TAG, "Banner ad failed to load: ${error.message}")
}

override fun onAdImpression() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ fun HomeScreen(
Button(
onClick = { navController.navigate(NavDestinations.Banner.name) },
enabled = uiState.canRequestAds,
modifier = modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
) {
Text(LocalContext.current.getString(R.string.nav_banner))
}
Button(
onClick = { navController.navigate(NavDestinations.LazyBanner.name) },
enabled = uiState.canRequestAds,
modifier = Modifier.fillMaxWidth(),
) {
Text(LocalContext.current.getString(R.string.nav_lazy_banner))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.gms.example.jetpackcomposedemo

import android.content.Context
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.jetpackcomposedemo.R
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.compose_util.BannerAd
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.BANNER_ADUNIT_ID
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.TAG
import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.suspendCancellableCoroutine

@Composable
fun LazyBannerScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
val isPreviewMode = LocalInspectionMode.current
val deviceCurrentWidth = LocalConfiguration.current.screenWidthDp
val adsToLoad = 5

// State for loading and completed.
var isLoading by remember { mutableStateOf(true) }
var loadedAds by remember { mutableStateOf<List<AdView>>(emptyList()) }
val tips = loadTips(context)

// Load ads when on launch of the composition.
LaunchedEffect(Unit) {
if (!isPreviewMode) {
loadedAds = loadBannerAds(context, adsToLoad, deviceCurrentWidth)
}
isLoading = false
}

Column {
// Display page subtitle.
Box(modifier.fillMaxWidth().background(MaterialTheme.colorScheme.primaryContainer)) {
Text(
text = context.getString(R.string.text_best_practices),
modifier.padding(8.dp),
style = MaterialTheme.typography.titleMedium,
)
}
// Display a loading indicator if ads are still being fetched.
if (isLoading) {
CircularProgressIndicator(modifier = modifier.width(100.dp).height(100.dp))
} else {
// Display a Lazy list of loaded ads.
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(loadedAds) { adView ->
Column {
BannerAd(adView, modifier.fillMaxWidth())
// Display the loaded content.
tips.forEach { content ->
Box(
modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.tertiaryContainer)
.padding(8.dp)
) {
Text(
text = content,
modifier.padding(8.dp),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
}
}
}
}

// Clean up the AdViews after use.
DisposableEffect(Unit) { onDispose { loadedAds.forEach { adView -> adView.destroy() } } }
}

@Preview
@Composable
private fun LazyBannerScreenPreview() {
JetpackComposeDemoTheme {
Surface(color = MaterialTheme.colorScheme.background) { LazyBannerScreen() }
}
}

private suspend fun loadBannerAd(context: Context, width: Int): AdView {
return suspendCancellableCoroutine { continuation ->
val adView = AdView(context)
adView.adUnitId = BANNER_ADUNIT_ID
val adSize = AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(context, width)
adView.setAdSize(adSize)
adView.adListener =
object : AdListener() {
override fun onAdLoaded() {
Log.d(TAG, "Banner ad was loaded.")
continuation.resume(adView)
}

override fun onAdFailedToLoad(error: LoadAdError) {
Log.e(TAG, "Banner ad failed to load: ${error.message}")
adView.destroy()
continuation.resumeWithException(Exception(error.message))
}

override fun onAdImpression() {
Log.d(TAG, "Banner ad had an impression.")
}

override fun onAdClicked() {
Log.d(TAG, "Banner ad was clicked.")
}
}

continuation.invokeOnCancellation {
Log.d(TAG, "Banner ad load was Cancelled.")
adView.destroy()
}

val adRequest = AdRequest.Builder().build()
adView.loadAd(adRequest)
}
}

private suspend fun loadBannerAds(context: Context, count: Int, width: Int): List<AdView> =
supervisorScope {
List(count) {
async {
try {
loadBannerAd(context, width)
} catch (error: java.lang.Exception) {
null
}
}
}
.awaitAll()
.filterNotNull()
}

fun loadTips(context: Context): List<String> {
val tipsArray = context.resources.getStringArray(R.array.tips)
return tipsArray.toList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ fun MainScreen(googleMobileAdsViewModel: MainViewModel, modifier: Modifier = Mod
NavHost(navController = navController, startDestination = NavDestinations.Home.name) {
composable(NavDestinations.Home.name) { HomeScreen(uiState, navController) }
composable(NavDestinations.Banner.name) { BannerScreen() }
composable(NavDestinations.LazyBanner.name) { LazyBannerScreen() }
}
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.google.android.gms.example.jetpackcomposedemo
enum class NavDestinations {
Home,
Banner,
LazyBanner,
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,4 @@ val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

val ColorStateLoaded = Color(0xFF009900)
val ColorStateUnloaded = Color(0xFFcc6600)
val ColorStateError = Color(0xFFcc0000)
val LightBlue = Color(0xFFADD8E6)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
<string name="main_title">Jetpack Compose Demo</string>
<string name="nav_home">Home</string>
<string name="nav_banner">Banner</string>
<string name="nav_lazy_banner">Lazy Banner</string>
<string name="privacy_options_open_button">Show Privacy Options Form</string>
<string name="text_best_practices">Google Mobile Ads best practices.</string>
<string name="text_reload">Reload Ad</string>
<string-array name="tips">
<item>Always use test ads when in development.</item>
<item>Use adaptive banners for optimized ad sizes across devices.</item>
<item>Respect user privacy by obtaining consent for personalized ads.</item>
<item>Mediate multiple ad networks for maximum revenue potential.</item>
<item>Prioritize user experience by minimizing ad load times.</item>
<item>Leverage Compose\'s declarative nature for clean ad integration.</item>
<item>Test ad placements thoroughly on various devices and screen sizes.</item>
</string-array>
</resources>

0 comments on commit 922b3f4

Please sign in to comment.