-
Notifications
You must be signed in to change notification settings - Fork 0
feat: AdMob 배너 광고 기능 추가 #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
- AdMob SDK 의존성 추가 및 초기화 - 홈 화면, 마이페이지에 배너 광고 추가 - 광고 클릭/노출 이벤트 로깅 추가 - Proguard 규칙 추가
WalkthroughAdMob 광고 플랫폼 통합을 추가합니다. 버전을 1.0.13에서 1.1.0으로 업데이트하고, AdMob SDK 의존성을 포함시키며, 앱 초기화 및 여러 UI 화면에 광고 배너를 구현합니다. 광고 이벤트는 Firebase Analytics로 추적됩니다. Changes
Sequence DiagramsequenceDiagram
participant App as FindUApp
participant AdMob as Google AdMob
participant Screen as UI Screen
participant Analytics as Firebase Analytics
App->>App: setDefaultNightMode (LIGHT)
App->>AdMob: MobileAds.initialize()
AdMob-->>App: ✓ Initialized
App->>Screen: Render Activity/Fragment
Screen->>Screen: Create AdView/AdBanner
Screen->>AdMob: loadAd(AdRequest)
AdMob-->>Screen: onAdLoaded
Screen->>Analytics: logEvent(AD_IMPRESSION, adLocation)
Analytics-->>Screen: ✓ Logged
Note over Screen: User interacts with ad
Screen->>AdMob: onClick
Screen->>Analytics: logEvent(AD_CLICK, adLocation)
Analytics-->>Screen: ✓ Logged
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@app/build.gradle.kts`:
- Around line 174-176: Update the hardcoded AdMob dependency to use the version
catalog and bump to the latest SDK: add a playServicesAds version "24.9.0" and a
play-services-ads library entry in gradle/libs.versions.toml, then replace the
implementation("com.google.android.gms:play-services-ads:23.1.0") line in
app/build.gradle.kts with the catalog reference (libs.play-services-ads) so the
project uses version cataloging and the updated 24.9.0 SDK.
In `@app/src/main/java/com/kuit/findu/presentation/ui/common/AdBanner.kt`:
- Around line 3-55: The AdView created in the AdBanner composable is never
cleaned up; update the AndroidView call inside AdBanner to supply an onRelease
callback that takes the created AdView and calls its destroy() (and optionally
clears adListener = null) to prevent resource leaks — locate the AndroidView
factory that constructs the AdView, add the onRelease parameter, and ensure you
call adView.destroy() (and null out adListener) in that callback.
In `@app/src/main/res/layout/fragment_my.xml`:
- Around line 465-471: The AdView with id ad_view_my is constrained to the
parent bottom and can overlap the ScrollView content; update the ScrollView's
bottom constraint so its bottom is anchored to the top of ad_view_my instead of
the parent. Locate the ScrollView element in the layout and change its bottom
constraint (e.g. layout_constraintBottom_toBottomOf="parent") to reference the
AdView (layout_constraintBottom_toTopOf="@id/ad_view_my") so the scrollable
content ends above the ad while keeping ad_view_my anchored to the parent's
bottom.
🧹 Nitpick comments (3)
app/src/main/java/com/kuit/findu/presentation/ui/my/MyFragment.kt (3)
35-35: 사용되지 않는 import가 있어요.
AdSize가 import 되어 있지만 이 파일에서는 사용되지 않네요. AdView의 크기가 XML 레이아웃에서 정의되어 있다면 이 import는 제거해도 될 것 같아요.🔧 제안하는 수정
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.LoadAdError
104-106: 에러 로깅 추가를 권장해요.
onAdFailedToLoad가 빈 상태로 남아있어요.AdBanner.kt에서는 에러 발생 시 로그를 남기고 있는데, 일관성을 위해 여기서도 동일하게 처리하면 디버깅에 도움이 될 거예요.🔧 제안하는 수정
override fun onAdFailedToLoad(error: LoadAdError) { - // 광고 로드 실패 시 처리 + android.util.Log.e("MyFragment", "광고 로드 실패: ${error.code} - ${error.message}") }
88-109: 코드 중복에 대한 고려가 필요해요.
setupAdView()의 광고 리스너 로직이AdBanner.kt컴포저블의 구현과 상당 부분 중복되고 있어요. 향후 유지보수를 고려하면 analytics 로깅 로직을 공통 유틸리티로 추출하거나, XML 기반 화면에서도 사용할 수 있는 헬퍼 함수를 만드는 것도 좋을 것 같아요.지금 당장 필수는 아니지만, 광고 배치가 더 늘어나면 리팩토링을 고려해 보세요.
|
|
||
| // AdMob | ||
| implementation("com.google.android.gms:play-services-ads:23.1.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check if gradle/libs.versions.toml exists and explore build structure
fd libs.versions.toml
# Also check the app/build.gradle.kts file to understand dependency usage
head -200 app/build.gradle.kts | grep -E "(implementation|libs\.|version)"Repository: FindYou-Kuit/FindYou-Android
Length of output: 2875
🌐 Web query:
Google Play Services Ads SDK latest version 2025 Android
💡 Result:
The latest Google Play services Ads SDK artifact (com.google.android.gms:play-services-ads) released in 2025 is 24.9.0 (released 2025‑12‑11). [1][2]
Gradle:
implementation "com.google.android.gms:play-services-ads:24.9.0"[2]
AdMob SDK 버전을 업데이트하고 버전 카탈로그 사용을 권장해요.
AdMob SDK 버전 23.1.0은 현재 많이 구버전이고, 최신 버전은 24.9.0입니다. 또한 다른 의존성들은 libs.* 버전 카탈로그를 사용하고 있는데, AdMob SDK만 하드코딩된 문자열로 되어 있어서 일관성 차원에서도 개선이 필요해요.
🔧 제안하는 수정
gradle/libs.versions.toml에 추가:
[versions]
playServicesAds = "24.9.0"
[libraries]
play-services-ads = { group = "com.google.android.gms", name = "play-services-ads", version.ref = "playServicesAds" }그 후 app/build.gradle.kts에서:
// AdMob
- implementation("com.google.android.gms:play-services-ads:23.1.0")
+ implementation(libs.play.services.ads)🤖 Prompt for AI Agents
In `@app/build.gradle.kts` around lines 174 - 176, Update the hardcoded AdMob
dependency to use the version catalog and bump to the latest SDK: add a
playServicesAds version "24.9.0" and a play-services-ads library entry in
gradle/libs.versions.toml, then replace the
implementation("com.google.android.gms:play-services-ads:23.1.0") line in
app/build.gradle.kts with the catalog reference (libs.play-services-ads) so the
project uses version cataloging and the updated 24.9.0 SDK.
| import android.os.Bundle | ||
| import android.util.Log | ||
| import androidx.compose.foundation.layout.fillMaxWidth | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.viewinterop.AndroidView | ||
| 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.firebase.analytics.FirebaseAnalytics | ||
| import com.kuit.findu.BuildConfig | ||
| import com.kuit.findu.analytics.AnalyticsEvent | ||
|
|
||
| private val AD_UNIT_ID = BuildConfig.ADMOB_BANNER_ID | ||
|
|
||
| @Composable | ||
| fun AdBanner( | ||
| modifier: Modifier = Modifier, | ||
| adLocation: String = "unknown" | ||
| ) { | ||
| AndroidView( | ||
| modifier = modifier.fillMaxWidth(), | ||
| factory = { context -> | ||
| val analytics = FirebaseAnalytics.getInstance(context) | ||
|
|
||
| AdView(context).apply { | ||
| setAdSize(AdSize.BANNER) | ||
| adUnitId = AD_UNIT_ID | ||
| adListener = object : AdListener() { | ||
| override fun onAdClicked() { | ||
| analytics.logEvent(AnalyticsEvent.AD_CLICK, Bundle().apply { | ||
| putString(AnalyticsEvent.AD_LOCATION, adLocation) | ||
| }) | ||
| } | ||
|
|
||
| override fun onAdImpression() { | ||
| analytics.logEvent(AnalyticsEvent.AD_IMPRESSION, Bundle().apply { | ||
| putString(AnalyticsEvent.AD_LOCATION, adLocation) | ||
| }) | ||
| } | ||
|
|
||
| override fun onAdFailedToLoad(error: LoadAdError) { | ||
| Log.e("AdBanner", "광고 로드 실패: ${error.code} - ${error.message}") | ||
| } | ||
|
|
||
| override fun onAdLoaded() { | ||
| Log.d("AdBanner", "광고 로드 성공") | ||
| } | ||
| } | ||
| loadAd(AdRequest.Builder().build()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Jetpack Compose AndroidView lifecycle AdView destroy cleanup best practice
💡 Result:
For an AdMob AdView inside AndroidView, the cleanup best practice is:
- Create/load the
AdViewinfactory(once) and update only cheap state inupdate(which can run many times). (developer.android.com) - Call
adView.destroy()fromAndroidView(onRelease = …), becauseonReleaseis specifically for “this View has exited the composition entirely and will not be reused again”. (developer.android.com) - Tie
pause()/resume()to the host lifecycle (e.g., via aLifecycleEventObserver), since Google’s reference showsresume()inonResume,pause()inonPause, anddestroy()inonDestroy. (developers.google.com) - If the
AndroidViewcan be reused (lists / movable content), optionally implementonResetfor “about to be reused in a different context” state-reset, and keep heavy cleanup foronRelease. (developer.android.com)
Compose example (recommended pattern)
`@Composable`
fun BannerAd(
adUnitId: String,
adSize: AdSize,
modifier: Modifier = Modifier,
) {
val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
modifier = modifier,
factory = { context ->
AdView(context).apply {
setAdUnitId(adUnitId)
setAdSize(adSize)
loadAd(AdRequest.Builder().build())
}
},
update = { adView ->
// Optional: attach lifecycle-driven pause/resume
val lifecycle = lifecycleOwner.lifecycle
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> adView.resume()
Lifecycle.Event.ON_PAUSE -> adView.pause()
else -> Unit
}
}
// Ensure we don't accumulate observers across recompositions
adView.setTag(R.id.adview_lifecycle_observer, observer)
lifecycle.addObserver(observer)
},
onReset = { adView ->
// If you opt into reuse, reset transient state here (don’t destroy).
adView.pause()
},
onRelease = { adView ->
// Permanent removal: free resources here.
adView.destroy()
}
)
}Key point: use onRelease for destroy() (permanent removal), and don’t call destroy() from update (runs repeatedly) or onReset (reuse path). (developer.android.com)
Citations:
- 1: https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/views-in-compose?utm_source=openai
- 2: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 3: https://developers.google.com/ad-manager/mobile-ads-sdk/android/reference/com/google/android/gms/ads/AdView?utm_source=openai
- 4: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 5: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
🌐 Web query:
Compose AndroidView factory disposal lifecycle management
💡 Result:
In AndroidView(...), the factory creates the View, and Compose manages that View instance across recompositions until it is either reused, temporarily detached, or permanently released. (developer.android.com)
What runs when
factory
- Called only when a new
Viewinstance is needed (first time, or after the prior one was permanently released). - Not called on every recomposition. (developer.android.com)
update
- Called after creation and on recompositions to push state into the existing
View. (developer.android.com)
onReset (reuse signal)
- Called when Compose intends to reuse the same
Viewinstance in a new context (e.g., movable content /ReusableContentHost). - If you don’t provide
onReset, Compose treats the view as not reusable and will discard it when moved/removed. (developer.android.com)
Temporary detach can happen
- Compose may remove the
Viewfrom its parent but keep it around for possible reuse (deactivated, not yet released). (developer.android.com)
onRelease (permanent disposal)
- Called when the
Viewexits the composition entirely and will never be reused. - After
onRelease, if the composable appears again later, Compose will callfactoryagain to create a freshView. (developer.android.com)
Practical pattern (cleanup goes in onRelease)
AndroidView(
factory = { context ->
MapView(context).apply { onCreate(null) }
},
update = { view ->
// push state into the view
},
onReset = { view ->
// prepare for reuse (clear listeners/transient state, cancel gestures, etc.)
},
onRelease = { view ->
// free resources; this is the "disposed forever" callback
view.onPause()
view.onDestroy()
}
)(What you do in onRelease depends on the specific View API; the key is: treat onRelease as final disposal.) (developer.android.com)
If you need “is it currently attached/active?”
- Use
View.addOnAttachStateChangeListener, or access the host lifecycle viafindViewTreeLifecycleOwner()(valid once attached). (developer.android.com)
Citations:
- 1: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 2: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 3: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 4: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 5: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 6: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 7: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
- 8: https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary?utm_source=openai
AdView 라이프사이클 정리가 없어 리소스 누수 위험이 있습니다.
Composable에서 AdView를 생성했는데 destroy 처리가 없어 리소스 누수 가능성이 있습니다. AndroidView의 onRelease 콜백을 사용해서 정리해 주세요.
🛠️ 수정 제안
import android.os.Bundle
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
private val AD_UNIT_ID = BuildConfig.ADMOB_BANNER_ID
@@ -20,6 +24,7 @@ fun AdBanner(
modifier: Modifier = Modifier,
adLocation: String = "unknown"
) {
+ val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { context ->
@@ -47,6 +52,27 @@ fun AdBanner(
loadAd(AdRequest.Builder().build())
}
}
+ update = { adView ->
+ val lifecycle = lifecycleOwner.lifecycle
+ val observer = LifecycleEventObserver { _, event ->
+ when (event) {
+ Lifecycle.Event.ON_RESUME -> adView.resume()
+ Lifecycle.Event.ON_PAUSE -> adView.pause()
+ else -> Unit
+ }
+ }
+ adView.setTag(R.id.adview_lifecycle_observer, observer)
+ lifecycle.addObserver(observer)
+ }
+ onRelease = { adView ->
+ adView.destroy()
+ }
)
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/kuit/findu/presentation/ui/common/AdBanner.kt` around
lines 3 - 55, The AdView created in the AdBanner composable is never cleaned up;
update the AndroidView call inside AdBanner to supply an onRelease callback that
takes the created AdView and calls its destroy() (and optionally clears
adListener = null) to prevent resource leaks — locate the AndroidView factory
that constructs the AdView, add the onRelease parameter, and ensure you call
adView.destroy() (and null out adListener) in that callback.
| <com.google.android.gms.ads.AdView | ||
| android:id="@+id/ad_view_my" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="wrap_content" | ||
| app:adSize="BANNER" | ||
| app:adUnitId="ca-app-pub-7675272869453438/1213424875" | ||
| app:layout_constraintBottom_toBottomOf="parent" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스크롤 영역과 광고가 겹칠 수 있습니다.
현재 ScrollView가 parent 하단까지 차지하고 AdView도 하단에 붙어 있어서, 콘텐츠가 광고 뒤로 가려질 수 있어요. ScrollView의 하단 제약을 AdView 상단으로 옮겨 주세요.
🛠️ 수정 제안
- <ScrollView
+ <ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/ad_view_my"
app:layout_constraintTop_toBottomOf="@id/cl_my_top_container"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="72dp">🤖 Prompt for AI Agents
In `@app/src/main/res/layout/fragment_my.xml` around lines 465 - 471, The AdView
with id ad_view_my is constrained to the parent bottom and can overlap the
ScrollView content; update the ScrollView's bottom constraint so its bottom is
anchored to the top of ad_view_my instead of the parent. Locate the ScrollView
element in the layout and change its bottom constraint (e.g.
layout_constraintBottom_toBottomOf="parent") to reference the AdView
(layout_constraintBottom_toTopOf="@id/ad_view_my") so the scrollable content
ends above the ad while keeping ad_view_my anchored to the parent's bottom.
🚀 이슈번호
N/A
✏️ 변경사항
📷 스크린샷
✍️ 사용법
🎸 기타
Summary by CodeRabbit
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.