Skip to content

Commit f125409

Browse files
Nicholas Ventimigliacopybara-github
authored andcommitted
Added Jetpack Compose LazyList Banner Sample.
PiperOrigin-RevId: 676619509
1 parent 05920e2 commit f125409

14 files changed

Lines changed: 236 additions & 26 deletions

File tree

kotlin/advanced/JetpackComposeDemo/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
android:value="true" />
2222

2323
<activity
24-
android:name="com.google.android.gms.example.jetpackcomposedemo.MainActivity"
24+
android:name="com.google.android.gms.example.jetpackcomposedemo.main.MainActivity"
2525
android:exported="true"
2626
android:theme="@style/Theme.JetpackComposeDemo">
2727
<intent-filter>

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/GoogleMobileAdsApplication.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ class GoogleMobileAdsApplication : Application() {
2222
companion object {
2323
const val TAG = "GoogleMobileAdsSample"
2424

25-
const val BANNER_ADUNIT_ID = "ca-app-pub-3940256099942544/9214589741"
25+
const val BANNER_AD_UNIT_ID = "ca-app-pub-3940256099942544/9214589741"
2626
}
2727
}

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/GoogleMobileAdsConsentManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package com.google.android.gms.example.jetpackcomposedemo
1818

1919
import android.app.Activity
2020
import android.content.Context
21-
import com.google.android.gms.example.jetpackcomposedemo.MainViewModel.Companion.TEST_DEVICE_HASHED_ID
21+
import com.google.android.gms.example.jetpackcomposedemo.main.MainViewModel.Companion.TEST_DEVICE_HASHED_ID
2222
import com.google.android.ump.ConsentDebugSettings
2323
import com.google.android.ump.ConsentForm.OnConsentFormDismissedListener
2424
import com.google.android.ump.ConsentInformation

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/NavDestinations.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/BannerScreen.kt renamed to kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/formats/BannerScreen.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.google.android.gms.example.jetpackcomposedemo
17+
package com.google.android.gms.example.jetpackcomposedemo.formats
1818

1919
import android.content.Context
2020
import android.util.Log
@@ -44,7 +44,7 @@ import com.google.android.gms.ads.AdSize
4444
import com.google.android.gms.ads.AdView
4545
import com.google.android.gms.ads.LoadAdError
4646
import com.google.android.gms.compose_util.BannerAd
47-
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.BANNER_ADUNIT_ID
47+
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.BANNER_AD_UNIT_ID
4848
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.TAG
4949
import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
5050

@@ -81,7 +81,7 @@ private fun loadAdaptiveBannerAd(context: Context, width: Int, isPreviewMode: Bo
8181
return adView
8282
}
8383

84-
adView.adUnitId = BANNER_ADUNIT_ID
84+
adView.adUnitId = BANNER_AD_UNIT_ID
8585
val adSize = AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(context, width)
8686
adView.setAdSize(adSize)
8787

@@ -92,11 +92,11 @@ private fun loadAdaptiveBannerAd(context: Context, width: Int, isPreviewMode: Bo
9292
}
9393

9494
override fun onAdFailedToLoad(error: LoadAdError) {
95-
Log.e(TAG, "Banner ad failed to load.")
95+
Log.e(TAG, "Banner ad failed to load: ${error.message}")
9696
}
9797

9898
override fun onAdImpression() {
99-
Log.d(TAG, "Banner ad had an impression.")
99+
Log.d(TAG, "Banner ad recorded an impression.")
100100
}
101101

102102
override fun onAdClicked() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.gms.example.jetpackcomposedemo.formats
18+
19+
import android.content.Context
20+
import android.util.Log
21+
import androidx.compose.foundation.background
22+
import androidx.compose.foundation.layout.Arrangement
23+
import androidx.compose.foundation.layout.Box
24+
import androidx.compose.foundation.layout.Column
25+
import androidx.compose.foundation.layout.fillMaxWidth
26+
import androidx.compose.foundation.layout.height
27+
import androidx.compose.foundation.layout.padding
28+
import androidx.compose.foundation.layout.width
29+
import androidx.compose.foundation.lazy.LazyColumn
30+
import androidx.compose.foundation.lazy.items
31+
import androidx.compose.material3.CircularProgressIndicator
32+
import androidx.compose.material3.MaterialTheme
33+
import androidx.compose.material3.Surface
34+
import androidx.compose.material3.Text
35+
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.DisposableEffect
37+
import androidx.compose.runtime.LaunchedEffect
38+
import androidx.compose.runtime.getValue
39+
import androidx.compose.runtime.mutableStateOf
40+
import androidx.compose.runtime.remember
41+
import androidx.compose.runtime.setValue
42+
import androidx.compose.ui.Modifier
43+
import androidx.compose.ui.platform.LocalConfiguration
44+
import androidx.compose.ui.platform.LocalContext
45+
import androidx.compose.ui.platform.LocalInspectionMode
46+
import androidx.compose.ui.tooling.preview.Preview
47+
import androidx.compose.ui.unit.dp
48+
import com.example.jetpackcomposedemo.R
49+
import com.google.android.gms.ads.AdListener
50+
import com.google.android.gms.ads.AdRequest
51+
import com.google.android.gms.ads.AdSize
52+
import com.google.android.gms.ads.AdView
53+
import com.google.android.gms.ads.LoadAdError
54+
import com.google.android.gms.compose_util.BannerAd
55+
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.BANNER_AD_UNIT_ID
56+
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication.Companion.TAG
57+
import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
58+
import kotlin.coroutines.resume
59+
import kotlinx.coroutines.async
60+
import kotlinx.coroutines.awaitAll
61+
import kotlinx.coroutines.supervisorScope
62+
import kotlinx.coroutines.suspendCancellableCoroutine
63+
64+
@Composable
65+
fun LazyBannerScreen(modifier: Modifier = Modifier) {
66+
val context = LocalContext.current
67+
val isPreviewMode = LocalInspectionMode.current
68+
val deviceCurrentWidth = LocalConfiguration.current.screenWidthDp
69+
val adsToLoad = 5
70+
71+
// Indicate whether the banner ads are currently being loaded.
72+
var isLoadingAds by remember { mutableStateOf(true) }
73+
var loadedAds by remember { mutableStateOf<List<AdView>>(emptyList()) }
74+
val fillerText = loadFillerText(context)
75+
76+
// Load ads when on launch of the composition.
77+
LaunchedEffect(Unit) {
78+
if (!isPreviewMode) {
79+
loadedAds = loadBannerAds(context, adsToLoad, deviceCurrentWidth)
80+
}
81+
isLoadingAds = false
82+
}
83+
84+
// Display a loading indicator if ads are still being fetched.
85+
if (isLoadingAds) {
86+
CircularProgressIndicator(modifier = modifier.width(100.dp).height(100.dp))
87+
} else {
88+
// Display a lazy list with loaded ads and filler content.
89+
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
90+
items(loadedAds) { adView ->
91+
Column {
92+
BannerAd(adView, modifier.fillMaxWidth())
93+
// Display the filler content.
94+
fillerText.forEach { content ->
95+
Box(
96+
modifier
97+
.fillMaxWidth()
98+
.background(MaterialTheme.colorScheme.primaryContainer)
99+
.padding(8.dp)
100+
) {
101+
Text(
102+
text = content,
103+
modifier.padding(8.dp),
104+
style = MaterialTheme.typography.bodyMedium,
105+
)
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
// Clean up the AdViews after use.
114+
DisposableEffect(Unit) { onDispose { loadedAds.forEach { adView -> adView.destroy() } } }
115+
}
116+
117+
@Preview
118+
@Composable
119+
private fun LazyBannerScreenPreview() {
120+
JetpackComposeDemoTheme {
121+
Surface(color = MaterialTheme.colorScheme.background) { LazyBannerScreen() }
122+
}
123+
}
124+
125+
private suspend fun loadBannerAd(context: Context, width: Int): Result<AdView> {
126+
return suspendCancellableCoroutine { continuation ->
127+
val adView = AdView(context)
128+
adView.adUnitId = BANNER_AD_UNIT_ID
129+
val adSize = AdSize.getCurrentOrientationInlineAdaptiveBannerAdSize(context, width)
130+
adView.setAdSize(adSize)
131+
adView.adListener =
132+
object : AdListener() {
133+
override fun onAdLoaded() {
134+
Log.d(TAG, "Banner ad was loaded.")
135+
continuation.resume(Result.success(adView))
136+
}
137+
138+
override fun onAdFailedToLoad(error: LoadAdError) {
139+
Log.e(TAG, "Banner ad failed to load: ${error.message}")
140+
adView.destroy()
141+
continuation.resume(Result.failure(Error(error.message)))
142+
}
143+
144+
override fun onAdImpression() {
145+
Log.d(TAG, "Banner ad recorded an impression.")
146+
}
147+
148+
override fun onAdClicked() {
149+
Log.d(TAG, "Banner ad was clicked.")
150+
}
151+
}
152+
153+
continuation.invokeOnCancellation {
154+
// When the coroutine is cancelled, clean up resources.
155+
adView.destroy()
156+
}
157+
158+
val adRequest = AdRequest.Builder().build()
159+
adView.loadAd(adRequest)
160+
}
161+
}
162+
163+
private suspend fun loadBannerAds(context: Context, count: Int, width: Int): List<AdView> =
164+
supervisorScope {
165+
List(count) {
166+
async {
167+
try {
168+
loadBannerAd(context, width).getOrNull()
169+
} catch (e: Error) {
170+
null
171+
}
172+
}
173+
}
174+
.awaitAll()
175+
.filterNotNull()
176+
}
177+
178+
fun loadFillerText(context: Context): List<String> {
179+
val fillerContent = context.resources.getStringArray(R.array.lazy_banner_filler_content)
180+
return fillerContent.toList()
181+
}

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/HomeScreen.kt renamed to kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/main/HomeScreen.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.google.android.gms.example.jetpackcomposedemo
1+
package com.google.android.gms.example.jetpackcomposedemo.main
22

33
import androidx.compose.foundation.layout.Column
44
import androidx.compose.foundation.layout.fillMaxWidth
@@ -25,10 +25,17 @@ fun HomeScreen(
2525
Button(
2626
onClick = { navController.navigate(NavDestinations.Banner.name) },
2727
enabled = uiState.canRequestAds,
28-
modifier = modifier.fillMaxWidth(),
28+
modifier = Modifier.fillMaxWidth(),
2929
) {
3030
Text(LocalContext.current.getString(R.string.nav_banner))
3131
}
32+
Button(
33+
onClick = { navController.navigate(NavDestinations.LazyBanner.name) },
34+
enabled = uiState.canRequestAds,
35+
modifier = Modifier.fillMaxWidth(),
36+
) {
37+
Text(LocalContext.current.getString(R.string.nav_lazy_banner))
38+
}
3239
}
3340
}
3441

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainActivity.kt renamed to kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/main/MainActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.google.android.gms.example.jetpackcomposedemo
17+
package com.google.android.gms.example.jetpackcomposedemo.main
1818

1919
import android.os.Bundle
2020
import android.util.Log
@@ -23,6 +23,7 @@ import androidx.activity.compose.setContent
2323
import androidx.activity.enableEdgeToEdge
2424
import androidx.lifecycle.lifecycleScope
2525
import com.google.android.gms.ads.MobileAds
26+
import com.google.android.gms.example.jetpackcomposedemo.GoogleMobileAdsApplication
2627
import kotlinx.coroutines.launch
2728

2829
class MainActivity : ComponentActivity() {

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainScreen.kt renamed to kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/main/MainScreen.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
package com.google.android.gms.example.jetpackcomposedemo
1+
package com.google.android.gms.example.jetpackcomposedemo.main
22

33
import android.content.Context
44
import android.content.ContextWrapper
55
import androidx.activity.ComponentActivity
6-
import androidx.compose.foundation.background
76
import androidx.compose.foundation.layout.Column
87
import androidx.compose.foundation.layout.Spacer
98
import androidx.compose.foundation.layout.WindowInsets
@@ -40,6 +39,8 @@ import androidx.navigation.compose.NavHost
4039
import androidx.navigation.compose.composable
4140
import androidx.navigation.compose.rememberNavController
4241
import com.example.jetpackcomposedemo.R
42+
import com.google.android.gms.example.jetpackcomposedemo.formats.BannerScreen
43+
import com.google.android.gms.example.jetpackcomposedemo.formats.LazyBannerScreen
4344
import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
4445

4546
@Composable
@@ -79,6 +80,7 @@ fun MainScreen(googleMobileAdsViewModel: MainViewModel, modifier: Modifier = Mod
7980
NavHost(navController = navController, startDestination = NavDestinations.Home.name) {
8081
composable(NavDestinations.Home.name) { HomeScreen(uiState, navController) }
8182
composable(NavDestinations.Banner.name) { BannerScreen() }
83+
composable(NavDestinations.LazyBanner.name) { LazyBannerScreen() }
8284
}
8385
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
8486
}
@@ -120,12 +122,18 @@ private fun MainTopBar(
120122
DropdownMenuItem(
121123
text = { Text(context.getString(R.string.adinspector_open_button)) },
122124
enabled = isMobileAdsInitialized,
123-
onClick = onOpenAdInspector,
125+
onClick = {
126+
menuExpanded = false
127+
onOpenAdInspector()
128+
},
124129
)
125130
if (isPrivacyOptionsRequired) {
126131
DropdownMenuItem(
127132
text = { Text(context.getString(R.string.privacy_options_open_button)) },
128-
onClick = onShowPrivacyOptionsForm,
133+
onClick = {
134+
menuExpanded = false
135+
onShowPrivacyOptionsForm()
136+
},
129137
)
130138
}
131139
}

kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainUiState.kt renamed to kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/main/MainUiState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.google.android.gms.example.jetpackcomposedemo
1+
package com.google.android.gms.example.jetpackcomposedemo.main
22

33
/** UiState for the MainViewModel. */
44
data class MainUiState(

0 commit comments

Comments
 (0)