Skip to content

Conversation

@ikseong00
Copy link
Collaborator

@ikseong00 ikseong00 commented Jan 28, 2026

🚀 이슈번호

N/A

✏️ 변경사항

  • AdMob SDK 의존성 추가 및 초기화
  • 홈 화면, 마이페이지에 배너 광고 추가
  • 광고 클릭/노출 이벤트 Firebase Analytics 로깅
  • Proguard 규칙 추가
  • 앱 버전 업데이트 (1.1.0)

📷 스크린샷

✍️ 사용법

🎸 기타

Summary by CodeRabbit

  • 새로운 기능

    • 버전 1.1.0으로 업데이트
    • 홈 화면과 마이 페이지에 광고 배너 추가
  • 개선 사항

    • 라이트 테마 기본 적용

✏️ Tip: You can customize this high-level summary in your review settings.

- AdMob SDK 의존성 추가 및 초기화
- 홈 화면, 마이페이지에 배너 광고 추가
- 광고 클릭/노출 이벤트 로깅 추가
- Proguard 규칙 추가
@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Walkthrough

AdMob 광고 플랫폼 통합을 추가합니다. 버전을 1.0.13에서 1.1.0으로 업데이트하고, AdMob SDK 의존성을 포함시키며, 앱 초기화 및 여러 UI 화면에 광고 배너를 구현합니다. 광고 이벤트는 Firebase Analytics로 추적됩니다.

Changes

코호트 / 파일(s) 변경 내용
빌드 설정 및 ProGuard
app/build.gradle.kts, app/proguard-rules.pro
versionCode/versionName을 15/"1.1.0"으로 업데이트, AdMob SDK(com.google.android.gms:play-services-ads:23.1.0) 의존성 추가, release/debug buildType에 ADMOB_BANNER_ID BuildConfig 필드 설정, release buildType에 signingConfig 바인딩, AdMob 클래스 ProGuard 규칙 추가
앱 초기화 및 메니페스트
app/src/main/AndroidManifest.xml, app/src/main/java/com/kuit/findu/FindUApp.kt
AdMob 애플리케이션 ID 메타데이터(ca-app-pub-7675272869453438~5374193050) 추가, 앱 시작 시 라이트 테마 강제 설정, MobileAds.initialize() 호출
분석 및 공통 UI 컴포넌트
app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt, app/src/main/java/com/kuit/findu/presentation/ui/common/AdBanner.kt
AD_CLICK, AD_IMPRESSION, AD_LOCATION 분석 이벤트 상수 추가, AdBanner Compose 함수 신규 작성(FirebaseAnalytics 통합, AdView 설정, 클릭/노출 이벤트 로깅)
홈 화면 UI 통합
app/src/main/java/com/kuit/findu/presentation/ui/home/component/HomeWebLinkList.kt, app/src/main/java/com/kuit/findu/presentation/ui/home/composeview/HomeScreen.kt
HomeWebLinkList에서 텍스트 정보 블록을 AdBanner로 대체(간격 조정), HomeScreen에 "home_screen" 위치의 AdBanner 추가
내 페이지 화면 UI 통합
app/src/main/java/com/kuit/findu/presentation/ui/my/MyFragment.kt, app/src/main/res/layout/fragment_my.xml
fragment_my.xml에 AdView(ad_unit_id: ca-app-pub-7675272869453438/1213424875) 추가, MyFragment에서 setupAdView() 메서드로 AdListener 연결 및 "my_page" 위치 분석 로깅

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • [Feat] 광고 삽입 #114: 메인 PR의 AdMob 의존성 추가, 메니페스트 메타데이터, AdView UI, AdBanner Composable, 광고 리스너, 분석 이벤트가 "[Feat] 광고 삽입" 기능을 직접 구현합니다.

Possibly related PRs

  • Fix qa #110: 두 PR 모두 app 레벨 설정을 수정하며, versionCode/versionName 업데이트 및 AndroidManifest에 AdMob 관련 항목 추가로 연관성이 있습니다.

Poem

🎬 광고의 물결이 흘러오고,
배너는 살금살금 화면을 채우고,
분석 이벤트가 쌓여가네—
벌스는 1.1.0으로 업! 🚀
이제 광고 꿈도 꾸겠지! 📺✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 템플릿 구조를 따르지만, 필수 정보 중 일부가 누락되었고 내용이 불완전합니다. Related issue 섹션에 이슈번호 작성 및 Screenshot과 사용법 섹션을 채워주세요. To Reviewers 섹션도 추가하면 좋습니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경사항의 핵심인 AdMob 배너 광고 기능 추가를 명확하게 설명하고 있으며, 간결하고 구체적입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/admob

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 기반 화면에서도 사용할 수 있는 헬퍼 함수를 만드는 것도 좋을 것 같아요.

지금 당장 필수는 아니지만, 광고 배치가 더 늘어나면 리팩토링을 고려해 보세요.

Comment on lines +174 to +176

// AdMob
implementation("com.google.android.gms:play-services-ads:23.1.0")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.

Comment on lines +3 to +55
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())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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:

  1. Create/load the AdView in factory (once) and update only cheap state in update (which can run many times). (developer.android.com)
  2. Call adView.destroy() from AndroidView(onRelease = …), because onRelease is specifically for “this View has exited the composition entirely and will not be reused again”. (developer.android.com)
  3. Tie pause()/resume() to the host lifecycle (e.g., via a LifecycleEventObserver), since Google’s reference shows resume() in onResume, pause() in onPause, and destroy() in onDestroy. (developers.google.com)
  4. If the AndroidView can be reused (lists / movable content), optionally implement onReset for “about to be reused in a different context” state-reset, and keep heavy cleanup for onRelease. (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:


🌐 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 View instance 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 View instance 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 View from its parent but keep it around for possible reuse (deactivated, not yet released). (developer.android.com)

onRelease (permanent disposal)

  • Called when the View exits the composition entirely and will never be reused.
  • After onRelease, if the composable appears again later, Compose will call factory again to create a fresh View. (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 via findViewTreeLifecycleOwner() (valid once attached). (developer.android.com)

Citations:


AdView 라이프사이클 정리가 없어 리소스 누수 위험이 있습니다.

Composable에서 AdView를 생성했는데 destroy 처리가 없어 리소스 누수 가능성이 있습니다. AndroidViewonRelease 콜백을 사용해서 정리해 주세요.

🛠️ 수정 제안
 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.

Comment on lines +465 to +471
<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" />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

스크롤 영역과 광고가 겹칠 수 있습니다.

현재 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants