Skip to content

Conversation

@alswlekk
Copy link
Collaborator

@alswlekk alswlekk commented Sep 21, 2025

🔗 관련 이슈

📙 작업 설명

  • 케어콜과 관련해서 FCM을 통한 알림 구현 코드를 추가했습니다.

  • 앱 내에서 알림 아이콘은 단색으로만 구현해야해서 일단 메디의 몸통으로 뜨도록 하고, 앱 밖에선 앱의 아이콘으로 상단 표시줄에 뜨게 됩니다.
    다음 문서들을 참고해 코드 작성했습니다
    https://firebase.google.com/docs/cloud-messaging/get-started?platform=android&hl=ko
    https://like-while.tistory.com/65
    추가적으로 SharedPerferences를 이용하는 방식에서 DataStore에 저장하는 방식으로 수정했습니다

  • 암호화 도구FcmTokenEncryptor

  • DataStore에 저장할 데이터 모델data class FcmToken(...)

  • DataStore Serializer (읽을 때 복호화 / 쓸 때 암호화)FcmTokenSerializer

  • 레포지토리 인터페이스FcmRepository

  • 레포지토리 구현체FcmRepositoryImpl

📸 스크린샷 또는 시연 영상 (선택)

기능 미리보기 기능 미리보기
image image

| 기능 설명 || 기능 설명 ||

💬 추가 설명 or 리뷰 포인트 (선택)

Summary by CodeRabbit

  • 새 기능

    • 푸시 알림 수신·표시 기능 추가 및 백그라운드 서비스 등록
    • 앱 시작 시 알림 권한 요청 UI 추가
    • FCM 토큰 암호화 저장 및 자동 동기화/검증 로직 도입
  • 변경 사항

    • 빌드 설정 및 의존성 업데이트(네트워킹, DataStore, Firebase 등)
    • 회원 가입 및 알림 관련 API/페이로드에 FCM 토큰 포함
    • 앱 아이콘·알람 아이콘 리소스 추가 및 CI/코드 스타일 검사 강화

@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Walkthrough

FCM 통합과 관련된 빌드·의존성 업데이트, 암호화된 DataStore 직렬화기 및 저장소 구현, Hilt 바인딩, FirebaseMessagingService 구현, 알림 채널/권한 UI, 리소스 및 CI 확장 작업이 추가되었습니다.

Changes

Cohort / File(s) 요약
빌드 설정 및 버전 관리
build.gradle.kts, app/build.gradle.kts, gradle/libs.versions.toml
Google Services 플러그인(alias) 추가/등록, buildConfig 활성화, AGP/Hilt/KSP 등 버전 업데이트 및 Firebase/Retrofit/OkHttp/Datastore/Coroutines/Detekt 등 의존성 추가·재배치
앱 매니페스트
app/src/main/AndroidManifest.xml
android.permission.POST_NOTIFICATIONS 권한 추가, com.google.firebase.messaging.default_notification_channel_id 메타데이터 추가, .data.api.fcm.FcmService 서비스 선언
앱 초기화 및 권한 요청 UI
app/src/main/java/com/konkuk/medicarecall/App.kt, app/src/main/java/com/konkuk/medicarecall/MainActivity.kt
앱 시작 시 알림 채널 생성 및 FCM 토큰 획득/저장 로직 추가, RequestNotificationPermission() 컴포저블 추가 및 호출
FCM 서비스 및 알림 처리
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt
FirebaseMessagingService 확장: 토큰 처리(onNewToken), 메시지 수신(onMessageReceived) 및 알림 생성/권한 체크 로직 구현, Hilt 주입
Repository 인터페이스 및 구현
app/src/main/java/.../data/repository/FcmRepository.kt, app/src/main/java/.../data/repositoryimpl/FcmRepositoryImpl.kt, app/src/main/java/.../data/di/RepositoryModule.kt
FcmRepository 인터페이스 추가, DataStore(암호화된) 기반 FcmRepositoryImpl 구현 및 Hilt 바인딩 메서드 추가
DataStore Serializer 및 암호화 유틸
app/src/main/java/.../data/util/FcmTokenSerializer.kt, app/src/main/java/.../data/util/FcmTokenEncryptor.kt
Datastore Serializer<FcmToken> 구현(암호화 + Base64), AndroidKeyStore 기반 AES-CBC 암복호화 유틸 추가
데이터 모델 / DTO 변경
app/src/main/java/.../data/model/FcmToken.kt, app/src/main/java/.../data/dto/request/MemberRegisterRequestDto.kt, app/src/main/java/.../data/dto/request/NotificationStatusRequestDto.kt
FcmToken 데이터 클래스 추가, MemberRegisterRequestDtofcmToken 필드 추가, 알림 상태 DTO 추가
API 인터페이스 / DI 제공자
app/src/main/java/.../data/api/fcm/FcmValidationService.kt, .../FcmUpdateService.kt, .../NotificationService.kt, app/src/main/java/.../data/di/ApiModule.kt
FCM 검증/업데이트/알림 상태용 Retrofit 인터페이스 추가 및 ApiModule에 제공자 추가
회원 등록/로그인 로직 변경
app/src/main/java/.../data/repository/MemberRegisterRepository.kt, .../repositoryimpl/MemberRegisterRepositoryImpl.kt, app/src/main/java/.../ui/feature/login/info/viewmodel/LoginViewModel.kt
회원 등록 API 시 fcmToken 전달 파라미터 추가, LoginViewModel에 FcmRepository 주입 및 토큰 검증/등록 흐름 추가
리소스(아이콘)
app/src/main/res/drawable/ic_alarm_logo.xml, app/src/main/res/drawable/ic_medi_app.xml
새 벡터 드로어블 자산 추가
CI 워크플로우
.github/workflows/android-ci.yml
Gradle/JDK 캐시 구성, secrets로 local.properties/google-services.json 생성, Detekt 검사 등 빌드 전 단계 추가

Sequence Diagram(s)

sequenceDiagram
    participant App as App.onCreate()
    participant FcmRepo as FcmRepository (impl)
    participant DataStore as Encrypted DataStore
    participant FCM as FirebaseMessaging
    participant Service as FcmService
    participant Notif as NotificationManager

    App->>App: createNotificationChannel()
    App->>FcmRepo: fetchAndStoreFcmToken()
    FcmRepo->>FCM: requestToken()
    FCM-->>FcmRepo: token
    FcmRepo->>DataStore: saveFcmToken(encrypt → base64)

    rect rgb(240,250,255)
    Note over FCM,Service: 푸시 수신 흐름
    FCM->>Service: onMessageReceived(remoteMessage)
    Service->>FcmRepo: getFcmToken()
    FcmRepo->>DataStore: readFcmToken()
    DataStore-->>FcmRepo: token(decrypted)
    Service->>Notif: showNotification(if POST_NOTIFICATIONS granted)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • 추가 주의점:
    • FcmTokenEncryptor.kt: AndroidKeyStore 키 생성 파라미터, CBC/IV/패딩 처리 검증
    • FcmTokenSerializer.kt: 스트림 I/O와 Base64 + 암호화/복호화 예외 처리
    • FcmService.kt / App.kt / MainActivity.kt: 권한 요청 흐름, PendingIntent 플래그 및 코루틴 스코프 생명주기
    • gradle/libs.versions.toml 및 빌드스크립트: 플러그인/의존성 버전 충돌 가능성

Possibly related PRs

  • #119 — 유사: AndroidKeyStore 기반 암호화 + Datastore Serializer로 토큰을 암호화해 저장하는 변경과 직접적인 코드 중복 가능성.
  • #118 — 유사: ApiModule/RepositoryModule DI 변경 및 FCM 관련 서비스·리포지토리 추가로 중복되는 DI/인터페이스 변경이 있음.

Suggested reviewers

  • ikseong00

Poem

🔔 토큰 하나 조용히 와서 숨었네,
금고 같은 Datastore에 암호화로 눕고,
채널이 열리면 알림은 안전히 울려,
권한 묻는 컴포저블 한 번 살짝 물어,
배포 전 밤에 모두 축하해 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경사항인 FCM 푸시알림 구현을 명확히 설명하고 있으며, 연결된 이슈 #110과 직접 관련됨.
Linked Issues check ✅ Passed FCM 토큰 수신, 알림 처리, Firebase 콘솔 등록 등 #110의 모든 주요 요구사항이 구현 코드로 충족됨.
Out of Scope Changes check ✅ Passed 모든 변경사항이 FCM 푸시알림 구현 및 DataStore 마이그레이션과 직접 관련되어 있으며, 범위를 벗어난 변경은 없음.
✨ 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/fcm-alarm-#110

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

@alswlekk alswlekk changed the title Feat: #110 케어콜 푸시알림 설정 [Feat] 케어콜 푸시알림 설정 #110 Sep 21, 2025
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: 7

🧹 Nitpick comments (11)
app/src/main/res/drawable/ic_medi_app.xml (2)

9-11: 하드코드 흰색(fillColor) 사용 — 테마/다크모드 대응 옵션

알림 smallIcon에서는 어차피 알파만 사용되므로 문제는 없지만, 앱 내 다른 곳에서 쓸 경우 다크모드에서 배경과 겹쳐 보이지 않을 수 있습니다.
옵션:

  • 사용처(ImageView/Compose)에서 tint를 적용해 제어
  • 또는 별도 tint 래퍼 drawable을 만들어 테마 색(?attr/colorOnSurface 등)로 틴트된 버전을 제공

브레이킹은 아니므로 선택사항으로 봅니다.


1-12: 알림 전용 아이콘 리소스 분리 권장

ic_medi_app이 알림 smallIcon으로 사용됩니다 (app/src/main/java/com/konkuk/medicarecall/data/api/FcmService.kt:66). 알림 전용 리소스(예: ic_notification_medi_app)로 분리해 알림용 패딩/스케일을 적용한 뒤 FcmService의 setSmallIcon 참조를 새 리소스로 변경하세요.

app/src/main/res/drawable/ic_alarm_logo.xml (1)

16-40: 다중 색상/스트로크 정리 제안

#FDD4D8, #DBF3E7 등 컬러와 strokeColor="#000000"이 포함되어 있어 알림 컨텍스트에서는 비일관적입니다. 알림용 자산은:

  • small icon: 단색(화이트) 알파 전용
  • large icon: 다색 가능(필요 시 setLargeIcon로 지정)
app/src/main/java/com/konkuk/medicarecall/App.kt (2)

27-31: 채널 이름/설명은 string 리소스로 이동 권장

현지화/가독성 위해 문자열 하드코딩 대신 @string 사용을 권합니다.

-            "FCM 알림명",
+            getString(R.string.fcm_channel_name),
...
-            description = "Firebase Cloud Messaging으로부터 수신된 알림을 표시합니다."
+            description = getString(R.string.fcm_channel_desc)

51-54: FCM 토큰 전체 로그 제거 권장

디버그라도 전체 토큰 로그는 리스크가 큽니다. 마스킹 로그만 남기세요.

-                    Log.d(TAG, "FCM token(full)=$token") // 여기에서 서버에 토큰 넘겨주면 됨
+                    // 서버 전송은 네트워크 계층에서 처리하고, 로그는 마스킹만 유지
app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (1)

189-213: Rationale/설정 이동 처리 추가 제안

shouldShowRequestPermissionRationale와 ‘다시 묻지 않음’ 케이스(설정 이동)를 처리하면 UX가 좋아집니다.

 @Composable
 fun RequestNotificationPermission() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
         val context = LocalContext.current
         val permission = android.Manifest.permission.POST_NOTIFICATIONS
+        val pm = android.content.pm.PackageManager.PERMISSION_GRANTED
+        val activity = (context as? android.app.Activity)

         val launcher = rememberLauncherForActivityResult(
             contract = ActivityResultContracts.RequestPermission()
         ) { isGranted ->
             if (isGranted) {
                 Toast.makeText(context, "알림 권한이 허용되었습니다.", Toast.LENGTH_SHORT).show()
             } else {
-                Toast.makeText(context, "알림 권한이 거부되었습니다.", Toast.LENGTH_SHORT).show()
+                val shouldShow = activity?.shouldShowRequestPermissionRationale(permission) ?: false
+                if (shouldShow) {
+                    Toast.makeText(context, "알림 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
+                } else {
+                    // '다시 묻지 않음' 케이스: 설정으로 유도
+                    Toast.makeText(context, "설정에서 알림 권한을 허용해주세요.", Toast.LENGTH_SHORT).show()
+                    val intent = android.content.Intent(
+                        android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS
+                    ).apply {
+                        putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, context.packageName)
+                    }
+                    context.startActivity(intent)
+                }
             }
         }

         LaunchedEffect(Unit) {
-            if (ContextCompat.checkSelfPermission(context, permission)
-                != android.content.pm.PackageManager.PERMISSION_GRANTED
-            ) {
+            if (ContextCompat.checkSelfPermission(context, permission) != pm) {
                 launcher.launch(permission)
             }
         }
     }
 }
app/src/main/java/com/konkuk/medicarecall/data/api/FcmService.kt (2)

76-86: 알림 권한 체크는 T+에서만 필요

API 33 미만에서는 권한 없이 notify 가능합니다. 분기 추가 권장.

-        if (ActivityCompat.checkSelfPermission(
-                this,
-                android.Manifest.permission.POST_NOTIFICATIONS
-            ) == android.content.pm.PackageManager.PERMISSION_GRANTED
-        ) {
+        val canNotify = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            ActivityCompat.checkSelfPermission(
+                this, android.Manifest.permission.POST_NOTIFICATIONS
+            ) == android.content.pm.PackageManager.PERMISSION_GRANTED
+        } else true
+        if (canNotify) {
             val notificationId = (0..Int.MAX_VALUE).random()
             NotificationManagerCompat.from(this).notify(notificationId, builder.build())
         } else {
             Log.w("FCM", "POST_NOTIFICATIONS permission not granted")
         }

32-34: 수신 로그는 디버그 빌드에서만

PII 가능성이 있어 릴리스에서는 숨기세요.

-        Log.d("FCM", "Message received: ${remoteMessage.data}")
+        if (com.konkuk.medicarecall.BuildConfig.DEBUG) {
+            Log.d("FCM", "Message received: ${remoteMessage.data}")
+        }
gradle/libs.versions.toml (3)

60-63: Firebase BoM 사용 시 개별 버전 고정 제거

BoM을 선언해두셨는데(firebase-bom), firebase-messaging에는 별도 버전(25.0.0)을 고정하고 있어 버전 정합성이 깨질 수 있습니다. BoM에 맞춰 firebase‑messaging의 버전 표기를 제거하는 쪽이 안전합니다.

아래처럼 정리 권장:

-[versions]
-...
-firebaseMessaging = "25.0.0"
-...
-[libraries]
-...
-firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" }
+[libraries]
+# BoM이 버전을 관리하므로 개별 버전 명시 제거
+firebase-messaging = { module = "com.google.firebase:firebase-messaging" }

Also applies to: 16-17


66-66: ui-tooling-preview alias 중복 정리

동일 모듈(androidx.compose.ui:ui-tooling-preview)에 대해 두 개의 alias(androidx-ui-tooling-preview, ui-tooling-preview)가 공존합니다. 액세서가 달라(예: libs.androidx.ui.tooling.preview vs libs.ui.tooling.preview) 혼선을 줄 수 있어 하나로 통일 권장합니다. 아래 항목(라인 66) 제거 제안.

-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }

Also applies to: 40-40


46-46: alias 명명 혼동 가능성: wear용 Material3

androidx-compose-material3가 wear 전용 아티팩트(androidx.wear.compose:compose-material3)를 가리키고, 일반 Material3는 androidx-material3로 별도 존재합니다. alias 이름이 헷갈릴 수 있어 wear-compose-material3 같은 명확한 이름으로 변경을 추천합니다. (후속 PR에서 리팩터링해도 됩니다)

Also applies to: 43-43

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b5ec28 and 93f165b.

📒 Files selected for processing (10)
  • app/build.gradle.kts (3 hunks)
  • app/src/main/AndroidManifest.xml (3 hunks)
  • app/src/main/java/com/konkuk/medicarecall/App.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (8 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/AppPreferences.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/FcmService.kt (1 hunks)
  • app/src/main/res/drawable/ic_alarm_logo.xml (1 hunks)
  • app/src/main/res/drawable/ic_medi_app.xml (1 hunks)
  • build.gradle.kts (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
🔇 Additional comments (22)
app/src/main/res/drawable/ic_medi_app.xml (2)

3-6: 정사각(24×24dp) 표시 vs 직사각 뷰포트(427×360) — 비율 불일치(수정 권고)

파일: app/src/main/res/drawable/ic_medi_app.xml (줄 3–6)
24×24dp 캔버스에 viewportWidth=427, viewportHeight=360을 쓰면 X/Y 스케일이 달라 로고가 눌려 보일 수 있습니다.

권장:

  • 24×24 아트보드로 재내보내기(패딩 포함).
  • 또는 viewportWidth/viewportHeight를 24/24로 변경하고 path를 스케일·센터 정렬.
  • 임시: 표시 크기를 비율 유지(24 * 360/427 ≈ 20.24dp)로 조정.

머지 전 시각적 확인 필요.


12-12: 해결: 파일 끝에 불필요 문자 없음 확인됨
app/src/main/res/drawable/ic_medi_app.xml의 12행()이 마지막 비어있지 않은 라인이며 그 이후 잔여 문자가 없습니다.

build.gradle.kts (2)

9-9: Google Services 플러그인 alias 추가 OK

하위 모듈에서만 적용되도록 apply false 처리 깔끔합니다. 앱 모듈에서 실제 적용 확인됐습니다.


1-12: 확인 완료 — google-services alias 매핑됨

libs.plugins.google.services는 libs.versions.toml의 'google-services'(alias)로 매핑되어 있으며 ID=com.google.gms.google-services, version.ref=googleService로 정의되어 있습니다 (libs.versions.toml:75).

app/src/main/java/com/konkuk/medicarecall/App.kt (1)

25-35: 알림 채널 생성 로직 LGTM

O+ 가드, 진동/배지/잠금화면 공개 설정까지 기본값 적절합니다.

app/src/main/AndroidManifest.xml (3)

8-8: POST_NOTIFICATIONS 권한 선언 OK

targetSdk 33+ 대응에 필요합니다.


26-28: 기본 채널 ID 일치성 확인

Manifest의 fcm_alertApp.FCM_CHANNEL_ID와 동일합니다. 상수/문자열 간 분리로 오탈자 위험이 있으니 PR 내에서 두 군데 모두 변경 시 함께 검토해주세요.


40-46: FCM 서비스 선언 OK

exported="false"MESSAGING_EVENT 인텐트 필터 적절합니다.

app/src/main/java/com/konkuk/medicarecall/data/api/AppPreferences.kt (1)

29-31: 민감 데이터 최소화 원칙

saveAccessToken/clearAccessToken 사용 경로에서 로그/크래시 리포트로 토큰이 흘러가지 않도록 주의하세요.

app/build.gradle.kts (9)

111-115: Firebase BOM + Messaging/Analytics 추가 OK

의존성 정합성 확보에 유용합니다.


59-62: buildConfig 활성화 OK

BuildConfig.DEBUG 사용과 일관됩니다.


65-75: Compose/AndroidX 추가 OK

foundation 추가 문제 없습니다.


116-118: Detekt formatting 플러그인 추가 OK

스타일 체크 일관성에 도움 됩니다.


97-102: Retrofit/OkHttp 버전 동기화 및 업그레이드 검토

Retrofit 3.0.0 및 com.squareup.retrofit2:converter-kotlinx-serialization:3.0.0이 출시되어 있습니다 — 현재 app/build.gradle.kts(97–102)에는 retrofit:2.12.0 / converter-kotlinx-serialization:2.12.0 / okhttp:4.10.0가 사용중입니다. 브레이킹 변경 여부를 확인하고 버전 동기화(또는 의도적 고정이면 이유 주석 추가) 및 OkHttp 상향을 진행하세요.


82-82: play-services-vision-common 사용 여부 확인 필요 (app/build.gradle.kts: 라인 82)

implementation(libs.play.services.vision.common)

rg 검색(패턴: com.google.android.gms.(vision|mlkit)) 결과가 비어 있어 사용 여부를 확정할 수 없습니다. 미사용이면 빌드 크기/DEX 감소를 위해 제거 권장. 코드베이스에서 해당 패키지/클래스 참조를 수동으로 검색하거나 모듈별 런타임 호출을 확인하세요.


1-118: 보안 스토리지 의존성 추가 — androidx.security:security-crypto-ktx:1.1.0 권장

AppPreferences 암호화를 위해 아래 의존성 추가 권장. 최신 안정판 확인: 1.1.0 (출시일: 2025-07-30).

 dependencies {
+    implementation("androidx.security:security-crypto-ktx:1.1.0") // 최신 안정판 확인 요망
 }

11-12: google-services 플러그인 적용 OK — google-services.json 존재 확인 필요

카탈로그(alias) 기반 적용은 일관됨. 리포지토리에서 google-services.json이 검색되지 않음. 루트와 android/app 경로에서 아래 명령으로 확인 후 경로(또는 부재) 보고.
fd -H --exact-depth 2 google-services.json | sed 's/^/FOUND: /' || true
find . -maxdepth 4 -type f -iname google-services.json -print | sed 's/^/FOUND: /' || true


93-95: 확인: kotlinx-serialization-json 1.8.0 ↔ Retrofit 컨버터 호환성 및 의존성 추가

    // Kotlin Serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
  • 조치 (app/build.gradle.kts — 줄 93–95): Retrofit용 컨버터 의존성 추가 — implementation("com.squareup.retrofit2:converter-kotlinx-serialization:<최신-2.x-버전>") (Maven Central의 최신 2.x 사용).
  • 검증: 통합·회귀 테스트 실행. 런타임/API/바이너리 호환성 문제 발견 시 kotlinx-serialization 런타임 또는 컨버터 버전을 Kotlin 툴체인에 맞춰 정렬.
gradle/libs.versions.toml (4)

64-64: kotlinx-metadata-jvm 의존성 사용 여부 재확인 필요

rg가 --type=kt 옵션을 인식하지 못해 검색이 실패했습니다 (unrecognized file type: kt). gradle/libs.versions.toml (line 64)에 선언된 kotlinx-metadata-jvm은 코드에서 직접(import kotlinx.metadata.*) 사용하지 않으면 앱 모듈에 둘 필요가 없습니다. 프로젝트 루트에서 아래 명령을 실행해 사용 여부 결과를 첨부하세요:

#!/bin/bash
set -euo pipefail
rg -nP '\bkotlinx\.metadata\b' -g '!**/build/**' || echo "No usage detected."
rg -n 'kotlinx-metadata-jvm|kotlinxMetadataJvm' -g '!**/build/**' || echo "No dependency reference detected."

18-18: 'play-services-vision-common' 사용 여부 재검증 필요

gradle/libs.versions.toml(예: gradle/libs.versions.toml, playServicesVisionCommon = "19.1.3")에 항목이 있습니다. FCM 전용이라면 vision-common은 대개 불필요해 APK/AAB 크기만 늘리므로 미사용이면 제거 권장.

검증 결과: 전달하신 스크립트가 rg의 'kt' 파일 타입을 인식하지 않아 검색이 정상 수행되지 않았습니다(출력: "unrecognized file type: kt"). 아래 명령으로 코드·빌드스크립트 전역을 다시 검색하세요:

#!/bin/bash
set -euo pipefail
# 코드 내 직접 사용 여부 확인
rg -nP -C2 '\bcom\.google\.android\.gms\.vision\b' -S -g '!**/build/**' -g '*.kt' -g '*.kts' -g '*.java' -g '*.xml' || echo "No direct usage found."
# 의존성/버전 카탈로그 선언 확인
rg -nP -C2 'play-services-vision-common|com\.google\.android\.gms:play-services-vision-common' -S -g '!**/build/**' -g '*.gradle' -g '*.kts' -g '*.toml' || echo "No dependency declaration found."

검색 결과가 없다면 gradle/libs.versions.toml의 해당 항목과(및) 빌드스크립트의 참조를 제거/정리하세요.


21-21: Hilt 컴파일러: KSP로 일관성 확인됨

app/build.gradle.kts:109에서 ksp(libs.hilt.compiler)를 사용 중이며, 프로젝트 전체에서 id("org.jetbrains.kotlin.kapt")가 발견되지 않았습니다. 혼용 이슈 없음 — 조치 불필요.


2-2: AGP(8.13.0) ↔ Gradle Wrapper(8.13) ↔ google-services(4.4.3) 호환성 수동 확인 필요
libs.versions.toml에 AGP 버전을 Gradle 래퍼(8.13) 버전과 동일하게 8.13.0으로 설정하셨는데, Android Gradle Plugin 8.13.0이 실제로 존재하는지 및 google-services(4.4.3) 플러그인과 함께 빌드/동기화에 문제 없는지 직접 검증 부탁드립니다.

Comment on lines 14 to 16
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

액세스 토큰 평문 저장 — 암호화 스토리지로 교체 권장

액세스 토큰은 EncryptedSharedPreferences로 저장하세요.

-    private val sharedPreferences: SharedPreferences =
-        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+    private val sharedPreferences: SharedPreferences by lazy {
+        val masterKey = androidx.security.crypto.MasterKey.Builder(context)
+            .setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme.AES256_GCM)
+            .build()
+        androidx.security.crypto.EncryptedSharedPreferences.create(
+            context,
+            PREFS_NAME,
+            masterKey,
+            androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+            androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+        )
+    }

추가로 app/build.gradle.ktsandroidx.security:security-crypto-ktx 의존성 추가 필요합니다(버전은 최신 안정판 확인 요망).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
private val sharedPreferences: SharedPreferences by lazy {
val masterKey = androidx.security.crypto.MasterKey.Builder(context)
.setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme.AES256_GCM)
.build()
androidx.security.crypto.EncryptedSharedPreferences.create(
context,
PREFS_NAME,
masterKey,
androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/AppPreferences.kt around
lines 14 to 16, the access token is being stored in plain SharedPreferences;
replace this with EncryptedSharedPreferences by adding the
androidx.security:security-crypto-ktx dependency in app/build.gradle.kts, create
or obtain a MasterKey (using the recommended scheme), instantiate
EncryptedSharedPreferences with that MasterKey and the same PREFS_NAME, and
update all get/set operations to use the encrypted prefs so tokens are stored
encrypted at rest; also handle migration of any existing plaintext values if
needed.


// 알림 빌더
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_medi_app) // 반드시 존재하는 리소스여야 함
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

❓ Verification inconclusive

small icon은 단색 알파 전용 리소스로

ic_medi_app가 컬러/런처 아이콘이면 부적절합니다. 단색 벡터(ic_stat_medi)로 교체하세요.

-            .setSmallIcon(R.drawable.ic_medi_app) // 반드시 존재하는 리소스여야 함
+            .setSmallIcon(R.drawable.ic_stat_medi) // 단색 알파 전용

작업 필요 — 알림 smallIcon을 단색(알파) 전용 아이콘으로 교체

R.drawable.ic_medi_app가 컬러/런처 아이콘이면 알림에서 색이 깨지므로 단색 벡터 리소스(ic_stat_medi)로 교체.

-            .setSmallIcon(R.drawable.ic_medi_app) // 반드시 존재하는 리소스여야 함
+            .setSmallIcon(R.drawable.ic_stat_medi) // 단색 알파 전용
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.setSmallIcon(R.drawable.ic_medi_app) // 반드시 존재하는 리소스여야 함
.setSmallIcon(R.drawable.ic_stat_medi) // 단색 알파 전용
🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/FcmService.kt around line
66, the notification smallIcon currently uses R.drawable.ic_medi_app which is a
colored/launcher asset; replace it with a single-color (alpha-only) vector
drawable R.drawable.ic_stat_medi, ensure the ic_stat_medi resource exists as a
monochrome vector (transparent background, single solid path) and update any
resource names or imports accordingly so setSmallIcon(R.drawable.ic_stat_medi)
is used for notifications.

.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(contentPi)
.setAutoCancel(true)
.setFullScreenIntent(contentPi, true) // 화면 꺼져있을 때 팝업
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Full-screen intent 남용 주의 (Android 14 정책)

긴급 케이스에만 사용하고, 사용자 동의 플래그(예: criticalPopupEnabled)로 게이트하세요.

-            .setFullScreenIntent(contentPi, true) // 화면 꺼져있을 때 팝업
+            .apply {
+                if (appPreferences.criticalPopupEnabled()) {
+                    setFullScreenIntent(contentPi, true)
+                }
+            }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/FcmService.kt around line
74, the call to .setFullScreenIntent(contentPi, true) must be gated by an
app-level user consent / emergency flag; modify the code to check a persisted
boolean (e.g., criticalPopupEnabled) and the notification urgency before calling
setFullScreenIntent, only invoking it when criticalPopupEnabled is true and the
message is an emergency, and ensure the flag defaults to false and is
configurable from user settings so full-screen intents are not used unless
explicitly enabled.

Comment on lines 90 to 92
// 알림 권한 요청
RequestNotificationPermission()

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

앱 시작 직후 권한 요청 UX 재고 필요

런치 즉시 권한 요청은 거부율을 올립니다. 교육용 UI/트리거 시점(예: 알림 기능 진입 시)로 미루는 것을 고려하세요.

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/MainActivity.kt around lines 90 to
92, the app currently requests notification permission immediately on launch
(RequestNotificationPermission()), which increases denial rates; remove that
immediate call from startup and instead invoke the permission flow at a
contextual trigger (e.g., when the user first navigates to or enables a
notification-related feature). Implement a lightweight educational UI/rationale
(dialog or in-app screen) before calling the permission API, and use
shouldShowRequestPermissionRationale to show the rationale on subsequent
attempts; ensure the startup path no longer calls
RequestNotificationPermission().

Comment on lines +1 to +43
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="42dp"
android:viewportWidth="42"
android:viewportHeight="42">
<group>
<clip-path
android:pathData="M10.12,0L31.879,0A10.12,10.12 0,0 1,42 10.12L42,31.879A10.12,10.12 0,0 1,31.879 42L10.12,42A10.12,10.12 0,0 1,0 31.879L0,10.12A10.12,10.12 0,0 1,10.12 0z"/>
<path
android:pathData="M10.12,0L31.879,0A10.12,10.12 0,0 1,42 10.12L42,31.879A10.12,10.12 0,0 1,31.879 42L10.12,42A10.12,10.12 0,0 1,0 31.879L0,10.12A10.12,10.12 0,0 1,10.12 0z"
android:fillColor="#00B859"/>
<group>
<clip-path
android:pathData="M5,16.016l41.025,-10.016l8.464,34.669l-41.025,10.016z"/>
<path
android:pathData="M46.484,22.159C45.678,21.713 44.858,21.378 44.058,21.153C40.94,13.755 33.221,9.432 25.649,11.281C18.078,13.129 13.251,20.475 13.854,28.447C13.207,29.037 12.593,29.749 12.05,30.566C9.881,33.834 9.689,37.53 11.621,38.82C12.625,39.491 14.007,39.377 15.407,38.656L15.408,38.66C16.84,44.527 22.745,48.124 28.596,46.696C29.762,46.411 30.596,45.479 30.82,44.374C31.753,44.329 32.692,44.194 33.628,43.966C34.564,43.737 35.38,43.451 36.194,43.085C36.904,43.948 38.066,44.382 39.222,44.1C45.075,42.671 48.656,36.757 47.224,30.891L47.222,30.885C48.802,30.882 50.084,30.346 50.667,29.286C51.787,27.25 49.914,24.06 46.482,22.158L46.484,22.159Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M38.436,29.158C40.704,28.604 42.093,26.311 41.538,24.037C40.982,21.763 38.694,20.368 36.425,20.922C34.157,21.476 32.769,23.768 33.324,26.043C33.879,28.317 36.168,29.711 38.436,29.158Z"
android:fillColor="#FDD4D8"/>
<path
android:pathData="M29.368,27.01C29.74,26.919 30.06,26.685 30.259,26.358C30.417,26.097 30.487,25.795 30.463,25.495C30.463,25.49 30.462,25.488 30.461,25.486C30.46,25.484 30.456,25.481 30.452,25.479C30.442,25.472 30.426,25.468 30.407,25.472L27.738,26.124C27.719,26.129 27.706,26.14 27.701,26.15C27.698,26.155 27.697,26.159 27.696,26.161C27.696,26.163 27.697,26.166 27.698,26.17C27.816,26.447 28.017,26.683 28.278,26.842C28.604,27.04 28.997,27.1 29.368,27.01Z"
android:strokeWidth="0.388534"
android:fillColor="#FF7272"
android:strokeColor="#000000"/>
<path
android:pathData="M22.375,33.079C24.643,32.526 26.031,30.233 25.476,27.959C24.921,25.685 22.632,24.29 20.364,24.844C18.096,25.398 16.707,27.69 17.262,29.965C17.817,32.239 20.106,33.633 22.375,33.079Z"
android:fillColor="#FDD4D8"/>
<path
android:pathData="M24.286,26.076C24.966,25.91 25.38,25.211 25.21,24.515C25.04,23.819 24.351,23.389 23.671,23.555C22.991,23.721 22.577,24.42 22.747,25.116C22.917,25.812 23.606,26.242 24.286,26.076Z"
android:fillColor="#231815"/>
<path
android:pathData="M33.544,23.816C34.225,23.65 34.638,22.951 34.468,22.255C34.298,21.559 33.609,21.129 32.929,21.296C32.249,21.462 31.836,22.16 32.005,22.856C32.175,23.552 32.864,23.982 33.544,23.816Z"
android:fillColor="#231815"/>
<path
android:pathData="M38.877,41.692C38.305,42.272 36.854,42.875 36.2,43.103C37.28,44.602 39.366,44.258 40.47,43.713C38.667,44.208 38.657,42.572 38.877,41.692Z"
android:fillColor="#DBF3E7"/>
<path
android:pathData="M14.508,34.751L15.47,38.692C14.682,39.174 13.455,39.274 12.939,39.264C14.008,38.749 14.43,36.041 14.508,34.751Z"
android:fillColor="#DBF3E7"/>
</group>
</group>
</vector>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

이 리소스를 상태바 small icon으로 쓰면 안 됩니다

상태바 small icon은 알파 전용(단색/투명)이어야 합니다. 이 벡터는 다중 색상/스트로크를 포함하므로 small icon로 쓰면 틴팅/가시성 문제가 생깁니다. small icon은 단색 벡터(ic_stat_*)로 별도 준비하고, 이 리소스는 필요 시 large icon/UI용으로만 사용하세요.

🤖 Prompt for AI Agents
app/src/main/res/drawable/ic_alarm_logo.xml lines 1-43: the reviewer says this
multi-color vector must not be used as a status-bar small icon (status icons
must be monochrome/alpha-only). Fix by creating a separate single-color,
alpha-only vector resource named with the ic_stat_* convention for the status
bar (remove multi-color fills and strokes, keep only shapes filled with a single
solid color or use themeable tint attribute), and leave ic_alarm_logo.xml for
large/UI use only; update any references (notifications, manifests) to point to
the new ic_stat_* drawable for small/status-bar icon usage.

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

🧹 Nitpick comments (4)
gradle/libs.versions.toml (4)

16-17: Firebase BOM 사용 시 개별 아티팩트 버전 제거 권장.

BOM을 쓰면 하위 모듈에는 버전을 지정하지 않는 게 안전합니다. firebase-messaging에 명시 버전이 있어 충돌/동결 위험이 있습니다. BOM만으로 정렬하도록 아래처럼 정리해 주세요.

적용 diff:

- firebaseMessaging = "25.0.0"
...
- firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" }
+ # (버전 키 제거)
...
+ firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }

추가로, 실제 모듈에서 반드시 implementation(platform(libs.firebase.bom)) 또는 dependencies { implementation(platform(...)) }가 먼저 선언돼 있어야 합니다.

Also applies to: 59-62


21-21: Hilt + KSP 전환 시 체크리스트.

  • hilt-compiler를 KSP로 사용하려면 각 모듈에서 ksp(libs.hilt.compiler)를 쓰고, 기존 kapt 의존성/플러그인은 제거해야 합니다.
  • ksp 플러그인 추가는 맞습니다. 빌드 캐시/증분 빌드 동작도 확인 권장.

예시(app/build.gradle.kts):

plugins {
  alias(libs.plugins.hilt)
  alias(libs.plugins.ksp)
}

dependencies {
  implementation(libs.hilt.android)
  ksp(libs.hilt.compiler)
  // kapt(...) 제거
}

Also applies to: 52-52, 72-73


65-65: 동일 모듈에 대한 중복 alias 정리.

이미 androidx-ui-tooling-preview(Line 40)가 존재합니다. 별칭 ui-tooling-preview는 혼선을 줄 수 있으니 하나로 통일하세요.

적용 diff:

- ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }

59-60: Firebase BOM/Analytics 추가는 방향성 OK.

BOM 도입과 Analytics 무버전 선언은 정석입니다. 메시징도 동일 패턴으로 맞추면 의존성 정렬이 더 안정적입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93f165b and 68042f2.

📒 Files selected for processing (2)
  • app/build.gradle.kts (3 hunks)
  • gradle/libs.versions.toml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/build.gradle.kts
🔇 Additional comments (5)
gradle/libs.versions.toml (5)

74-74: Google Services 플러그인 버전 고정 LGTM.

버전 카탈로그로 고정한 점 좋습니다. 모듈 적용 위치가 app 모듈에 한정(루트 빌드스크립트가 아닌)되어 있는지도 확인만 부탁드립니다. 또한 google-services.json이 모든 빌드 변형/플레이버에 존재하는지 점검해 주세요.


18-18: play-services-vision-common 제거 검토 필요

gradle/libs.versions.toml에 playServicesVisionCommon = "19.1.3"가 등록되어 있으나 repo 검색(rg) 결과 Vision 관련 식별자(com.google.android.gms.vision 또는 'Vision')가 발견되지 않았습니다. 제거 전 실제 사용처(레거시 ML Vision API, 외부 모듈/플러그인, CI 스크립트 등)를 수동으로 확인하고, 사용처가 없다면 제거하세요.

위치: gradle/libs.versions.toml (라인 18) — playServicesVisionCommon = "19.1.3"


2-2: 확인: Gradle 래퍼(8.13)가 AGP 8.13.0 요구사항을 충족합니다
gradle/wrapper/gradle-wrapper.properties — distributionUrl=…/gradle-8.13-bin.zip: AGP 8.13.0의 최소 요구 Gradle 8.13을 만족합니다.


23-23: KSP 패치 버전 업데이트 권장 — Kotlin 2.0.21용 최신 패치: 2.0.21-1.0.28

Maven Central 및 KSP 릴리스 자료에 따르면 Kotlin 2.0.21에 빌드된 최신 KSP 패치는 2.0.21-1.0.28입니다.

- ksp = "2.0.21-1.0.25"
+ ksp = "2.0.21-1.0.28"

공식 릴리스 노트로 호환성/변경사항 확인 후 적용하세요.


8-8: kotlinx-metadata-jvm 사용 여부 재확인 필요

제공한 rg 명령이 출력 없이 종료됐습니다 — 사용되지 않는다는 증거가 아니므로 제거 전 아래 추가 검증을 실행하고 출력 결과를 코멘트로 올려라.

  • 코드베이스 전체에서 패키지/아티팩트명 검색:
rg -nP -C2 'org\.jetbrains\.kotlinx:kotlinx-metadata-jvm|kotlinx-metadata-jvm|kotlinx\.metadata' || true
  • Gradle 의존성 리포트에서 전이적 포함 확인:
./gradlew dependencies --no-daemon | rg -n 'kotlinx-metadata|kotlinx.metadata' || true
  • 모듈별로 확인이 필요하면(예시):
./gradlew :<module>:dependencies --configuration runtimeClasspath | rg -n 'kotlinx-metadata|kotlinx.metadata' || true

위치: gradle/libs.versions.toml (line 8) — kotlinxMetadataJvm = "0.9.0"

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

🧹 Nitpick comments (6)
app/build.gradle.kts (3)

75-80: Material3 중복/혼선 가능성: 두 아티팩트 동시 의존 확인

libs.androidx.material3(표준 Compose M3)와 libs.androidx.compose.material3(toml에서 wear compose 계열로 보임)를 동시에 추가했습니다. 실제로 wear용이 필요 없다면 하나로 정리하세요. wear용이 필요하다면 toml 좌표가 정확한지 재검(아래 toml 코멘트 참조).


94-96: kotlinx-serialization 버전 하드코딩 지양, 버전 카탈로그로 이관 권장

Kotlin(2.0.21)과의 호환성 관리 측면에서 toml에 버전/라이브러리를 정의해 사용하세요.

다음 diff로 정리 제안(toml 변경 포함):

-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
+    implementation(libs.kotlinx.serialization.json)

추가로 toml 변경은 해당 파일 코멘트를 참고해주세요.


112-116: Firebase BOM 사용은 좋습니다. toml에서 모듈 버전 제거 필요

BOM을 쓸 때 개별 모듈(예: firebase-messaging)은 버전을 명시하지 않는 것이 정석입니다. 현재 toml에 버전이 있어 충돌/예상치 못한 업그레이드가 날 수 있으니 아래 toml 코멘트를 적용해 주세요.

gradle/libs.versions.toml (3)

65-68: 중복 별칭 정리: ui-tooling-preview가 두 곳에 정의됨

androidx-ui-tooling-preview와 별도로 ui-tooling-preview가 동일 모듈을 가리킵니다. 하나로 통일하세요.

제안(diff: 보조 별칭 삭제):

-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }

61-63: Analytics 포함 의도 확인

개인정보/동의 정책 영향이 있으니 애널리틱스 사용 범위를 명확히 하시고, 필요 없다면 제거를 고려하세요.


61-61: 버전 카탈로그에 serialization 라이브러리 추가 제안

app 모듈에서 하드코딩하지 않도록 아래 항목을 추가하면 좋습니다.

적용 diff:

+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.8.0" }

필요 시 최신 안정판으로 조정 가능합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68042f2 and dac99e8.

📒 Files selected for processing (2)
  • app/build.gradle.kts (4 hunks)
  • gradle/libs.versions.toml (2 hunks)
🔇 Additional comments (9)
app/build.gradle.kts (5)

59-62: buildConfig 활성화 LGTM

BuildConfig.BASE_URL 사용 전제의 설정 일관성이 좋아졌습니다. 추가 조치 없음.


117-118: Detekt formatting 플러그인 연동 LGTM

팀 컨벤션 정합성 향상에 도움됩니다.


11-12: 조치 필요 — settings.gradle(.kts) pluginManagement에 google() 등록 및 app/google-services.json 확인

app/build.gradle.kts에서 plugins DSL로 google services 플러그인(alias(libs.plugins.google.services)) 적용은 맞습니다.
스크립트 실행 결과: settings.gradle(.kts)에서 pluginManagement.repositories에 google() 항목이 검색되지 않았고, app/google-services.json 파일이 발견되지 않았습니다.

  • settings.gradle(.kts)에 pluginManagement { repositories { google() ... } } 를 추가하거나(또는 CI/루트 설정에서 google()이 제공되는지 확인).
  • app/google-services.json을 app/에 배치하거나(또는 Firebase 사용이 아니라면 플러그인 제거/대체).

파일: app/build.gradle.kts (Lines 11-12) — 해당 플러그인 적용부 확인.


97-103: OkHttp 업그레이드 권장 — 4.10.0 → 5.1.0 (보안/버그 픽스)

app/build.gradle.kts(97–103)에서 okhttp:4.10.0 사용 중; 최신 안정판은 OkHttp 5.1.0(2025-07-07), 최소 Android API는 21입니다. 메이저 업그레이드(4→5)이니 Retrofit 컨버터·logging-interceptor 등 호환성 확인 및 전체 테스트 후 버전 업데이트 PR 제출하세요.


83-84: play-services-vision-common 사용 여부 확인 — 제거 검토
app/build.gradle.kts (줄 83–84): implementation(libs.play.services.vision.common). .kt/.java 파일에서 'com.google.android.gms.vision' 또는 'com.google.android.libraries.vision'을 검색했으나 사용처가 발견되지 않았습니다. FCM/푸시에는 불필요하므로 실제 사용처가 없다면 제거해 메서드 수와 APK 크기를 줄이세요.

gradle/libs.versions.toml (4)

24-25: KSP 버전 호환성 확인

ksp = "2.0.21-1.0.25"는 Kotlin 2.0.21과 매칭되지만, AGP/Gradle 조합에 따라 마이너 업데이트가 필요할 수 있습니다. CI에서 annotation processing 관련 경고가 없는지 확인 부탁드립니다.


69-77: google-services 플러그인 선언 LGTM

버전 카탈로그로 관리되어 일관성이 좋습니다.


8-8: kotlinx-metadata-jvm 추가 목적 확인 — 현재 레포에서 참조 없음

레포 전역 검색 결과 코드·빌드스크립트에서 사용 흔적이 없고 gradle/libs.versions.toml에만 선언되어 있습니다 (gradle/libs.versions.toml:8, 65). 사용 목적이 없으면 버전 카탈로그에서 제거하고, 사용 중이면 사용 위치(파일)와 이유를 PR에 명시하세요.


2-2: AGP 8.13 호환성 확인 — 별도 조치 불필요

gradle/wrapper/gradle-wrapper.properties가 Gradle 8.13을 가리키고, CI(.github/workflows/android-ci.yml)에서 JDK 17을 사용하도록 설정되어 있어 AGP 8.13의 최소 요구사항(Gradle 8.13, JDK 17)을 충족합니다. (developer.android.com)

확인 위치: gradle/wrapper/gradle-wrapper.properties (distributionUrl=gradle-8.13-bin.zip), .github/workflows/android-ci.yml (actions/setup-java: java-version '17').

Comment on lines 119 to 120
ksp(libs.hilt.manager)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

중요: ksp(libs.hilt.manager)는 빌드 실패 가능성이 큼(안드로이드X Hilt Compiler는 KSP 미지원)

androidx.hilt:hilt-compiler는 현재 KAPT만 지원합니다. KSP로 연결하면 빌드가 깨질 확률이 높습니다. 실제로 필요 없다면 제거, 필요하다면 KAPT로 전환하세요.

적용 diff(불필요 시 제거):

-    ksp(libs.hilt.manager)

만약 WorkManager 등의 AndroidX Hilt 확장에 컴파일러가 필요하면 다음처럼 전환:

// plugins 블록에 추가
plugins {
    id("org.jetbrains.kotlin.kapt")
}

// dependencies 블록
kapt(libs.hilt.manager)

또는 해당 확장을 쓰지 않는다면 toml의 hilt-manager 항목 자체를 삭제하세요(아래 toml 코멘트 참조).

🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 119-120, the dependency uses
ksp(libs.hilt.manager) which will likely break the build because
androidx.hilt:hilt-compiler only supports KAPT; either remove the dependency if
unused, or switch to KAPT: add the Kotlin KAPT plugin
(id("org.jetbrains.kotlin.kapt")) to the plugins block and replace
ksp(libs.hilt.manager) with kapt(libs.hilt.manager); alternatively, if you truly
don’t use the Hilt WorkManager/AndroidX extensions, delete the hilt-manager
entry from libs.versions.toml instead.

Comment on lines 47 to 48
androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }

androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wear Compose Material3 좌표 의심됨 또는 불필요한 중복

androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", ... }는 실제 아티팩트 명이 material3로 알려져 있습니다. 의도적으로 Wear용을 쓰는 게 아니라면 삭제, 필요하다면 name을 material3로 수정하세요. 또한 app 모듈에서 표준 Material3도 함께 쓰고 있어 중복입니다.

제안(diff 1: 삭제):

-androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }

제안(diff 2: Wear 필요 시 수정):

-androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }
+androidx-compose-material3 = { group = "androidx.wear.compose", name = "material3", version.ref = "composeMaterial3" }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
androidx-compose-material3 = { group = "androidx.wear.compose", name = "compose-material3", version.ref = "composeMaterial3" }
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around lines 47 to 48, the dependency entry uses
the Wear Compose artifact coordinates (group = "androidx.wear.compose", name =
"compose-material3") which is likely incorrect or redundant with the standard
Material3 dependency used by the app; either remove this Wear-specific entry if
not needed, or change the name to the correct artifact name ("material3") and/or
group to the standard compose material3 coordinates so it matches the actual
artifact you intend to use and avoid duplicate Material3 dependencies across
modules.

Comment on lines 51 to +56
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
hilt-manager = {group = "androidx.hilt" , name = "hilt-compiler", version.ref = "hiltManager"}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

안드로이드X Hilt 컴파일러 항목(hilt-manager) 문제: 좌표/버전/도구 체인

  • hilt-manager 라이브러리 이름이 혼동을 유발하고, 좌표가 androidx.hilt:hilt-compiler로 보이지만 버전 키(hiltManager = "1.0.0")가 오래되었을 수 있음.
  • 해당 컴파일러는 KSP 미지원(KAPT 필요). 현재 app 모듈에서 ksp(libs.hilt.manager)로 사용 중이라 빌드 실패 가능.

불필요하면 완전 제거, 필요하면 KAPT 전환과 최신 버전 확인을 권장합니다.

제안(diff: 제거):

-hilt-manager = {group = "androidx.hilt" , name = "hilt-compiler", version.ref = "hiltManager"}

필요 시 별도 이슈로 최신 호환 버전 검증 도와드릴게요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
hilt-manager = {group = "androidx.hilt" , name = "hilt-compiler", version.ref = "hiltManager"}
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around lines 52-56, the hilt-manager entry is
incorrect/problematic: it declares androidx.hilt:hilt-compiler under a confusing
name, likely points to an old version that requires KAPT (not KSP) and your app
currently uses ksp(libs.hilt.manager), which will cause build failures. Fix by
either removing the hilt-manager entry if unused, or update the dependency to
the correct Hilt compiler artifact and version key and switch the app to use
kapt(...) instead of ksp(...); after changing, verify and update the version.ref
to a current compatible release.

Comment on lines +61 to +64
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" }

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Firebase BOM 사용 시 개별 모듈 버전 제거

firebase-bom을 사용하므로 firebase-messagingversion.ref는 제거해야 일관적으로 BOM에 의해 버전이 고정됩니다.

적용 diff:

-firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" }
+firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version.ref = "firebaseMessaging" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around lines 61 to 64, the firebase-messaging entry
includes a version.ref which conflicts with using firebase-bom; remove the
version.ref from the firebase-messaging declaration so its version is governed
by firebase-bom (leave the group/name keys or alternatively convert the entry to
the same module shorthand as google-firebase-analytics), ensuring consistency
with the BOM.

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

🧹 Nitpick comments (3)
.github/workflows/android-ci.yml (3)

4-7: 브랜치 트리거 구성은 적절합니다. 수동 실행·중복 실행 취소·권한 최소화만 추가해요.

develop 대상 push/PR만 타게팅한 점 좋습니다. 운영 편의·보안 위해 workflow_dispatch, concurrency(cancel-in-progress), permissions(contents: read) 추가를 추천합니다.

 on:
   push:
     branches: [ "develop" ]
   pull_request:
     branches: [ "develop" ]
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true

26-35: Gradle 캐시가 이중화되어 캐시 미스/쓰래시 가능성 있습니다.

setup-java@v4의 cache: gradle를 이미 사용 중이므로 별도의 actions/cache는 제거하세요.

-      - name: Cache Gradle packages
-        uses: actions/cache@v4
-        with:
-          path: |
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }}
-          restore-keys: |
-            ${{ runner.os }}-gradle-

36-39: local.properties 작성은 >> 대신 안전한 heredoc으로 덮어쓰기 권장

여러 번 실행 시 누적을 막고, 멀티라인/특수문자 보존을 위해 heredoc이 안전합니다.

-      - name: Generate local.properties
-        run: |
-          echo '${{ secrets.LOCAL_PROPERTIES }}' >> ./local.properties
+      - name: Generate local.properties
+        run: |
+          cat > local.properties <<'EOF'
+          ${{ secrets.LOCAL_PROPERTIES }}
+          EOF
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dac99e8 and 590709d.

📒 Files selected for processing (1)
  • .github/workflows/android-ci.yml (2 hunks)
🔇 Additional comments (2)
.github/workflows/android-ci.yml (2)

9-9: jobs 블록 구조 변경 사항 문제 없습니다.

구조·명명 컨벤션 무난합니다.


40-57: 더미 google-services.json: 패키지명 검증 및 파일 존재 시 생성 건너뛰기 추가 권장

검증 결과: 제공된 스크립트 실행에서 app/google-services.json 없음; build.gradle / AndroidManifest에서 applicationId/package 미검출 — 패키지명 확인 불가.
권장 조치: 아래 diff 적용하고 PACKAGE_NAME에 실제 applicationId를 설정하거나 워크플로우에서 동적으로 추출하도록 변경하세요.

-      - name: Create dummy google-services.json
-        run: |
-          cat <<EOF > app/google-services.json
+      - name: Create dummy google-services.json (if missing)
+        env:
+          PACKAGE_NAME: com.konkuk.medicarecally
+        run: |
+          if [ -f app/google-services.json ]; then
+            echo "google-services.json already present; skipping dummy creation."
+            exit 0
+          fi
+          cat > app/google-services.json <<EOF
           {
             "project_info": {
               "project_number": "000000000000",
               "project_id": "dummy-project"
             },
             "client": [{
               "client_info": {
                 "mobilesdk_app_id": "1:000000000000:android:0000000000000000",
                 "android_client_info": {
-                  "package_name": "com.konkuk.medicarecally"
+                  "package_name": "${PACKAGE_NAME}"
                 }
               }
             }]
           }
           EOF

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

🧹 Nitpick comments (4)
.github/workflows/android-ci.yml (4)

40-57: 더미 google-services.json은 debug 소스셋에만 두세요

루트 모듈(app/google-services.json)에 쓰면 향후 release 빌드에도 섞일 리스크가 있습니다. app/src/debug/google-services.json로 한정하면 안전합니다. 디렉토리 생성을 포함해 아래처럼 수정 권장합니다.

-      - name: Create dummy google-services.json
-        run: |
-          cat <<EOF > app/google-services.json
+      - name: Create dummy google-services.json (debug only)
+        run: |
+          mkdir -p app/src/debug
+          cat <<'EOF' > app/src/debug/google-services.json
           {
             "project_info": {
               "project_number": "000000000000",
               "project_id": "dummy-project"
             },
             "client": [{
               "client_info": {
                 "mobilesdk_app_id": "1:000000000000:android:0000000000000000",
                 "android_client_info": {
                   "package_name": "com.konkuk.medicarecall"
                 }
               }
             }]
           }
           EOF

참고:

  • productFlavor를 쓰는 경우에는 app/src/<flavorDebug>/google-services.json로 더 좁힐지 검토 부탁드립니다.
  • 사용 중인 com.google.gms.google-services 플러그인 버전에 따라 api_key 필드가 필요할 수 있습니다. 현재 스키마로 빌드가 통과하는지 확인해 주세요.

4-7: 트리거 유연성/소음 감소 옵션

운영에 도움될 수 있는 두 가지 제안입니다.

  • 수동 실행 허용:
 on:
   push:
     branches: [ "develop" ]
   pull_request:
     branches: [ "develop" ]
+  workflow_dispatch:
  • 문서만 변경 시 빌드 스킵(원하면):
   pull_request:
-    branches: [ "develop" ]
+    branches: [ "develop" ]
+    paths-ignore:
+      - '**/*.md'

9-13: 권한 최소화·Wrapper 검증·중복 캐시 정리

몇 가지 CI 위생 개선 제안입니다.

  • Job 권한 최소화:
 jobs:
   ci-build:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
  • Gradle Wrapper 서명 검증 추가(서드파티 감염 방지):
       - uses: actions/checkout@v4
+      - name: Validate Gradle Wrapper
+        uses: gradle/wrapper-validation-action@v2
  • 캐시 중복 제거: actions/setup-java@v4cache: gradle과 별도의 actions/cache@v4가 동일 경로를 이중 캐싱하고 있습니다. 하나로 통일하세요. 권장안은 Gradle 공식 액션입니다.
-      - name: Cache Gradle packages
-        uses: actions/cache@v4
-        with:
-          path: |
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }}
-          restore-keys: |
-            ${{ runner.os }}-gradle-
+      - name: Gradle Build Action (caching)
+        uses: gradle/gradle-build-action@v3

검증 부탁드리는 점:

  • 위 변경 후 캐시 적중률 및 빌드 시간 비교.

36-39: local.properties 주입: 덮어쓰기, 비밀 노출, 포크 PR 호환성

현재 >> 추가 모드와 echo 사용은 재시도 시 중복/개행 이슈가 있고, 포크 PR에서는 시크릿이 비어 빌드가 깨질 수 있습니다. 또한 명령 로그에 문자열이 노출될 소지가 있습니다(마스킹되더라도 보수적으로 처리 권장).

아래처럼 조건부·덮어쓰기·표준출력 노출 최소화로 바꿔주세요:

-      - name: Generate local.properties
-        run: |
-          echo '${{ secrets.LOCAL_PROPERTIES }}' >> ./local.properties
+      - name: Generate local.properties
+        if: ${{ secrets.LOCAL_PROPERTIES != '' }}
+        env:
+          LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
+        run: |
+          printf "%s" "$LOCAL_PROPERTIES" > local.properties

추가 확인:

  • LOCAL_PROPERTIES에 sdk.dir=가 포함돼 있으면 GitHub Runner 경로와 불일치로 빌드가 실패할 수 있습니다. 포함되어 있다면 제거하거나 CI에서 설정하지 않도록 해주세요.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 590709d and a771654.

📒 Files selected for processing (1)
  • .github/workflows/android-ci.yml (2 hunks)
🔇 Additional comments (1)
.github/workflows/android-ci.yml (1)

59-60: assembleDebug로 교체 권장 — buildDebug는 표준 태스크가 아닙니다

AGP 표준 태스크는 assembleDebug/bundleDebug입니다. buildDebug는 기본 제공되지 않아 CI가 실패할 수 있습니다.

다음으로 교체해 주세요:

-        run: ./gradlew buildDebug --stacktrace
+        run: ./gradlew assembleDebug --stacktrace

AAB를 빌드하려면 bundleDebug를 사용하세요.

Copy link
Contributor

@ikseong00 ikseong00 left a comment

Choose a reason for hiding this comment

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

레퍼런스 파일 첨부 좋습니다!
ci 파일 체크만 부탁드려요~

Comment on lines 40 to 60
- name: Create dummy google-services.json
run: |
cat <<EOF > app/google-services.json
{
"project_info": {
"project_number": "000000000000",
"project_id": "dummy-project"
},
"client": [{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
"android_client_info": {
"package_name": "com.konkuk.medicarecall"
}
}
}],
"api_key": [{
"current_key": "DUMMY_API_KEY"
}]
}
EOF
Copy link
Contributor

Choose a reason for hiding this comment

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

요 부분은 아래 ci 파일 참고해서 작성해주세요!
secret 으로 넘겨야합니다..
https://github.com/OurMenu/OurMenu-Android-V2/blob/develop/.github/workflows/android-ci.yml

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

🧹 Nitpick comments (3)
.github/workflows/android-ci.yml (3)

26-35: Gradle 캐시 이중 적용(중복 캐싱) — 하나만 사용하세요

setup-java의 cache: gradle와 actions/cache가 둘 다 Gradle 캐시를 다뤄 중복입니다. 하나만 유지하세요(보통 setup-java만).

불필요한 캐시 스텝 제거:

-      - name: Cache Gradle packages
-        uses: actions/cache@v4
-        with:
-          path: |
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/buildSrc/**/*.kt') }}
-          restore-keys: |
-            ${{ runner.os }}-gradle-

36-39: local.properties 파일 쓰기 방식 개선(append → overwrite, 안전한 출력)

는 중복 내용을 쌓습니다. 또한 echo보다 printf가 이스케이프에 안전합니다. 시크릿 가드도 함께 권장.

-      - name: Generate local.properties
-        run: |
-          echo '${{ secrets.LOCAL_PROPERTIES }}' >> ./local.properties
+      - name: Generate local.properties
+        if: ${{ secrets.LOCAL_PROPERTIES != '' }}
+        run: |
+          printf '%s' "${{ secrets.LOCAL_PROPERTIES }}" > ./local.properties

41-43: google-services.json 쓰기 방식 개선 및 시크릿 가드

같은 이유로 overwrite/printf 권장 + 시크릿 없을 때 스킵.

-      - name: Generate google-services.json
-        run: |
-          echo '${{ secrets.GOOGLE_SERVICES }}' >> ./app/google-services.json
+      - name: Generate google-services.json
+        if: ${{ secrets.GOOGLE_SERVICES != '' }}
+        run: |
+          printf '%s' "${{ secrets.GOOGLE_SERVICES }}" > ./app/google-services.json

옵션(로그 안전성 더 강화): 시크릿을 base64로 저장해 두고 CI에서 decode

- name: Generate google-services.json (base64)
  if: ${{ secrets.GOOGLE_SERVICES_B64 != '' }}
  run: |
    echo "${{ secrets.GOOGLE_SERVICES_B64 }}" | base64 -d > app/google-services.json
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a771654 and d51baa8.

📒 Files selected for processing (2)
  • .github/workflows/android-ci.yml (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt
🔇 Additional comments (1)
.github/workflows/android-ci.yml (1)

49-50: assembleDebug 전환 LGTM

비표준 buildDebug → 표준 assembleDebug로 수정된 점 좋습니다.

Comment on lines +4 to +7
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
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

포크 PR에서 시크릿 미제공 시 빌드 깨짐 위험 — 시크릿 의존 스텝 가드 필요

fork에서 오는 pull_request 이벤트엔 시크릿이 전달되지 않아 google-services.json/local.properties 생성 스텝이 빈 파일을 만들거나 실패 → 빌드 실패로 이어질 수 있습니다. 아래처럼 시크릿 존재 여부로 가드하세요.

추천 변경(해당 스텝 라인에서 if 추가):

-      - name: Generate local.properties
-        run: |
-          echo '${{ secrets.LOCAL_PROPERTIES }}' >> ./local.properties
+      - name: Generate local.properties
+        if: ${{ secrets.LOCAL_PROPERTIES != '' }}
+        run: |
+          printf '%s' "${{ secrets.LOCAL_PROPERTIES }}" > ./local.properties

-      - name: Generate google-services.json
-        run: |
-          echo '${{ secrets.GOOGLE_SERVICES }}' >> ./app/google-services.json
+      - name: Generate google-services.json
+        if: ${{ secrets.GOOGLE_SERVICES != '' }}
+        run: |
+          printf '%s' "${{ secrets.GOOGLE_SERVICES }}" > ./app/google-services.json

옵션(포크 PR용 스텁 파일 생성 — 시크릿 없을 때만):

- name: Create stub google-services.json for fork PRs
  if: ${{ github.event_name == 'pull_request' && secrets.GOOGLE_SERVICES == '' }}
  run: |
    printf '{ "project_info": {}, "client": [] }' > app/google-services.json

옵션(중복 러너 낭비 방지):

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
🤖 Prompt for AI Agents
.github/workflows/android-ci.yml lines 4-7: pull_request workflows from forks
don't have repository secrets, so steps that rely on GOOGLE_SERVICES or other
secrets can create empty files or fail and break the build; add guards on those
steps to only run when the secret exists (e.g., if: github.event_name ==
'pull_request' && secrets.GOOGLE_SERVICES != '') or when running on push, and
optionally add a fallback step that creates a minimal stub
google-services.json/local.properties only when the secret is missing (if:
github.event_name == 'pull_request' && secrets.GOOGLE_SERVICES == '') so fork
PRs succeed; also consider adding a concurrency block to avoid duplicate runner
usage (concurrency group and cancel-in-progress: true).

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d51baa8 and 49cd59c.

📒 Files selected for processing (1)
  • .github/workflows/android-ci.yml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/android-ci.yml

28-28: the runner of "actions/cache@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (1)
.github/workflows/android-ci.yml (1)

17-24: 포크 PR에서도 빌드가 깨지지 않도록 시크릿 가드를 추가해주세요.

포크에서 올라오는 pull_request 러너에는 시크릿이 비어 있어도 이 스텝이 그대로 실행돼 빈 local.properties/google-services.json이 생성되고, 이후 Gradle 빌드가 바로 실패합니다. 이전에도 동일 우려가 공유됐는데 아직 적용되지 않았습니다. 시크릿 존재 여부로 가드하고, 내용은 printf로 그대로 써 주는 편이 안전합니다.

-    - name: Generate local.properties
-      run: |
-        echo "${{ secrets.LOCAL_PROPERTIES }}" > ./local.properties
+    - name: Generate local.properties
+      if: ${{ secrets.LOCAL_PROPERTIES != '' }}
+      run: |
+        printf '%s' "${{ secrets.LOCAL_PROPERTIES }}" > ./local.properties
@@
-    - name: Generate google-services.json
-      run: |
-        echo "${{ secrets.GOOGLE_SERVICES }}" > ./app/google-services.json
+    - name: Generate google-services.json
+      if: ${{ secrets.GOOGLE_SERVICES != '' }}
+      run: |
+        printf '%s' "${{ secrets.GOOGLE_SERVICES }}" > ./app/google-services.json

Updated the Android CI workflow to generate google-services.json using Base64 decoding and added comments for clarity.
Copy link
Collaborator

@librawish808 librawish808 left a comment

Choose a reason for hiding this comment

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

주석 덕분에 코드 흐름을 이해할 수 있었습니다👍

@ProtossManse
Copy link
Collaborator

CI 아직도 실패했다고 뜨는데 혹시 해결 안된걸까요?

지민님이 말씀해주신대로 SharedPreferences -> DataStore로 바꾸는 게 좋을 것 같습니다 !

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

🧹 Nitpick comments (4)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (4)

37-37: serviceScope 생명주기 관리를 고려해보세요.

serviceScope가 생성되었지만 onDestroy()에서 취소되지 않네요. FirebaseMessagingService는 시스템이 관리하는 서비스라 일반적으로 큰 문제는 없지만, 명시적으로 정리해주는 게 더 안전할 수 있어요.

필요하다면 다음과 같이 추가할 수 있어요:

override fun onDestroy() {
    serviceScope.cancel()
    super.onDestroy()
}

54-54: 서버 토큰 업데이트 로직 구현이 필요해요.

TODO 주석이 남아있네요. FCM 토큰이 변경되면 서버에도 알려줘야 정상적으로 푸시를 받을 수 있어요.

이 부분 구현을 도와드릴까요? 예를 들어 FcmRepository나 별도의 API 인터페이스에 updateFcmTokenToServer(token: String) 같은 메서드를 추가하는 코드를 생성해드릴 수 있어요.


126-126: 알림 ID 관리 방식을 고려해보세요.

랜덤 ID를 사용하면 나중에 특정 알림을 업데이트하거나 취소할 수 없어요. 케어콜 특성상 알림을 관리해야 할 수도 있으니, 의미있는 ID 체계를 고려해보는 게 좋을 것 같아요.

예를 들어:

  • remoteMessage.data["notification_id"]에서 ID를 가져오거나
  • 알림 타입별로 고정 ID를 사용하는 방식도 가능해요

118-118: Full screen intent 사용을 재검토해보세요.

setFullScreenIntent(contentPi, true)는 화면이 꺼져있을 때 앱을 전체화면으로 띄워요. 긴급 알림이 아니라면 사용자 경험 측면에서 다소 침해적일 수 있어요.

케어콜의 알림 특성상 긴급성이 높다면 괜찮지만, 그렇지 않다면 제거하거나 특정 알림 타입에만 적용하는 걸 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b75e47e and 61421e1.

📒 Files selected for processing (8)
  • app/src/main/java/com/konkuk/medicarecall/App.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/model/FcmToken.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/FcmRepository.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/util/FcmTokenEncryptor.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/util/FcmTokenSerializer.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/FcmRepository.kt
🧰 Additional context used
🪛 GitHub Actions: Android CI
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt

[error] 69-69: NoConsecutiveBlankLines: Needless blank line(s)


[error] 81-81: NoConsecutiveBlankLines: Needless blank line(s)


[error] 19-19: NoUnusedImports: Unused import


[error] 27-27: NoUnusedImports: Unused import


[error] 28-28: NoUnusedImports: Unused import


[error] 40-40: NoUnusedImports: Unused import

app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt

[error] 21-21: TrailingCommaOnDeclarationSite: Missing trailing comma before ")"


[error] 6-6: NoUnusedImports: Unused import

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt

[error] 41-41: BlockCommentInitialStarAlignment: Initial star should align with start of block comment


[error] 77-77: Indentation: Unexpected indentation (12) (should be 16) [Indentation]


[error] 78-78: Indentation: Unexpected indentation (16) (should be 20) [Indentation]


[error] 79-79: Indentation: Unexpected indentation (16) (should be 20) [Indentation]


[error] 80-80: Indentation: Unexpected indentation (16) (should be 20) [Indentation]


[error] 81-81: Indentation: Unexpected indentation (12) (should be 16) [Indentation]


[error] 82-82: Indentation: Unexpected indentation (16) (should be 20) [Indentation]


[error] 83-83: Indentation: Unexpected indentation (16) (should be 20) [Indentation]


[error] 84-84: Indentation: Unexpected indentation (12) (should be 16) [Indentation]


[error] 85-85: Indentation: Unexpected indentation (12) (should be 16) [Indentation]

🔇 Additional comments (1)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

62-64: 좋아요!

메시지 수신 시 로깅하고 알림을 표시하는 로직이 깔끔하네요.

Comment on lines +24 to +35
val encryptedBytesDecoded = Base64.getDecoder().decode(raw)
val decryptedBytes = FcmTokenEncryptor.decrypt(encryptedBytesDecoded)
val decodedJsonString = decryptedBytes.decodeToString()
return Json.decodeFromString(decodedJsonString)
}

override suspend fun writeTo(t: FcmToken, output: OutputStream) {
val json = Json.Default.encodeToString(t)
val bytes = json.toByteArray()
val encryptedBytes = FcmTokenEncryptor.encrypt(bytes)
val encryptedBytesBase64 = Base64.getEncoder().encode(encryptedBytes)

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

API 26 미만 기기에서 Base64 사용 시 크래시 발생
java.util.Base64 클래스는 Android API 26 이상에서만 제공됩니다. 우리 앱(minSdk 24 기준)처럼 26 미만 기기를 지원하면 DataStore를 읽거나 쓸 때 NoClassDefFoundError가 발생해 앱이 바로 죽습니다. android.util.Base64(NO_WRAP 플래그)로 대체해 주세요.

- import java.util.Base64
+import android.util.Base64
@@
-        val encryptedBytesDecoded = Base64.getDecoder().decode(raw)
+        val encryptedBytesDecoded = Base64.decode(raw, Base64.NO_WRAP)
@@
-        val encryptedBytesBase64 = Base64.getEncoder().encode(encryptedBytes)
+        val encryptedBytesBase64 = Base64.encode(encryptedBytes, Base64.NO_WRAP)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/util/FcmTokenSerializer.kt
around lines 24 to 35, the code uses java.util.Base64 which is only available on
API 26+ and will crash on minSdk 24; replace usages with android.util.Base64
(use Base64.decode(raw, Base64.NO_WRAP) to get bytes and
Base64.encode(encryptedBytes, Base64.NO_WRAP) to produce a byte[]/String as
needed), update imports to android.util.Base64, and ensure types match (convert
byte[] to String when necessary) so DataStore read/write works on API <26.

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

♻️ Duplicate comments (1)
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1)

75-75: 불필요한 빈 줄 제거가 필요해요.

CI가 여기서 걸리고 있네요. 라인 75의 빈 줄 때문에 린터 에러가 발생하고 있어요. 이 빈 줄만 제거하면 바로 통과할 거예요.

다음 diff를 적용해주세요:

 }
 
-
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun MedicineDetailLayout(
🧹 Nitpick comments (1)
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1)

31-35: clearToken 구현 패턴 확인이 필요해요.

saveFcmToken에서는 it.copy(fcmToken = token) 패턴을 사용하는데, clearToken에서는 새 인스턴스 FcmToken(null)을 생성하고 있습니다.

FcmToken이 단일 필드만 가진다면 문제없지만, 일관성을 위해 다음과 같이 수정하는 것을 고려해보세요:

 override suspend fun clearToken() {
     context.fcmDataStore.updateData {
-        FcmToken(null)
+        it.copy(fcmToken = null)
     }
 }

이렇게 하면 FcmToken에 나중에 필드가 추가되어도 다른 필드 값들이 보존됩니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61421e1 and 9f43a53.

📒 Files selected for processing (3)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt
🧰 Additional context used
🪛 GitHub Actions: Android CI
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt

[error] 75-75: Detekt: Needless blank line(s) [NoConsecutiveBlankLines]

🔇 Additional comments (5)
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1)

76-86: MedicineDetailLayout 추출은 좋은 개선이에요.

UI 로직을 별도 함수로 분리하신 건 테스트 가능성과 재사용성 측면에서 좋은 선택이에요. 함수 시그니처도 깔끔하고 매개변수 구성도 적절하네요.

app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (4)

1-11: 임포트 정리 완료!

과거 리뷰에서 지적된 불필요한 import가 깔끔하게 제거되었네요. 현재 모든 import가 실제로 사용되고 있습니다.


13-16: DataStore 설정 잘 되어있어요.

FcmTokenSerializer를 통한 암호화도 제대로 적용되었고, 파일명도 명확합니다. PR 목표대로 SharedPreferences에서 DataStore로 마이그레이션이 잘 이루어졌네요.


18-21: DI 구성 완벽합니다!

과거 리뷰에서 지적된 trailing comma도 추가되었고, @Singleton@ApplicationContext 사용이 적절합니다. Repository 패턴과 Hilt 설정이 깔끔하게 되어있네요.


22-24: 토큰 저장 로직 깔끔해요.

updateDatacopy 패턴을 사용해서 안전하게 업데이트하고 있습니다.

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

♻️ Duplicate comments (3)
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1)

18-21: 아직 trailing comma가 빠져있어요 (Pipeline 실패 중)

이전 리뷰에서 지적된 부분인데 아직 수정이 안 된 것 같아요. 마지막 파라미터 뒤에 콤마를 추가해주세요.

 class FcmRepositoryImpl @Inject constructor(
-    @ApplicationContext private val context: Context
+    @ApplicationContext private val context: Context,
 ) : FcmRepository {
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (2)

7-7: 사용하지 않는 import를 제거하세요

ContentValues.TAG를 import했지만 134번 라인에서 자체 TAG를 정의하고 있어서 이 import는 불필요해요.

-import android.content.ContentValues.TAG

39-41: 블록 주석 포맷을 수정해주세요 (Pipeline 실패 중)

이전 리뷰에서 지적되었지만 아직 수정이 안 된 것 같아요. 별표 정렬을 맞춰주세요.

-    /* FCM에서 해당 기기의 토큰 바꼈다고 알려줄 때 호출되는 콜백(앱 처음 설치, 앱데이터 삭제, FCM 내부 정책 변경 등)
-    FCM쪽에서 알아서 불러주는 콜백(개발자가 직접 호출 X,호출됐을 때 어떻게 저장할 지에 대한 코드)
-    * */
+    /*
+     * FCM에서 해당 기기의 토큰 바꼈다고 알려줄 때 호출되는 콜백(앱 처음 설치, 앱데이터 삭제, FCM 내부 정책 변경 등)
+     * FCM쪽에서 알아서 불러주는 콜백(개발자가 직접 호출 X,호출됐을 때 어떻게 저장할 지에 대한 코드)
+     */
🧹 Nitpick comments (1)
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1)

31-35: 일관성을 위해 copy 사용을 권장해요

saveFcmToken에서는 it.copy(fcmToken = ...)를 사용하는데, 여기서는 새 인스턴스를 생성하고 있어요. 일관성을 위해 같은 방식을 사용하는 게 좋을 것 같아요.

     override suspend fun clearToken() {
         context.fcmDataStore.updateData {
-            FcmToken(null)
+            it.copy(fcmToken = null)
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f43a53 and 66cd9e9.

📒 Files selected for processing (3)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt
🧰 Additional context used
🪛 GitHub Actions: Android CI
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt

[error] 20-20: Detekt: TrailingCommaOnDeclarationSite - Missing trailing comma before ")"

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt

[error] 41-41: Detekt: BlockCommentInitialStarAlignment - Initial star should align with start of block comment

🔇 Additional comments (9)
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (3)

13-16: DataStore 설정 깔끔하네요!

암호화된 serializer를 사용해서 FCM 토큰을 안전하게 저장하는 구조가 잘 구성되어 있어요.


22-24: 저장 로직 깔끔해요

updateDatacopy를 사용한 간결한 구현이 좋습니다.


26-29: 조회 로직도 잘 구현되어 있어요

data.first()로 현재 값을 가져오는 표준 패턴이네요.

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (6)

31-37: DI 설정과 코루틴 스코프가 잘 구성되어 있어요

이전 리뷰에서 제안된 SupervisorJob 사용이 적용되어 에러 전파를 방지하고 있네요.


42-59: 토큰 저장 로직이 잘 구현되어 있어요

이전 리뷰에서 제안된 try-catch 에러 핸들링이 추가되어 있고, 로깅도 적절하네요. 54번 라인의 서버 API 호출 관련 주석도 나중에 구현할 때 도움이 될 것 같아요.


61-65: 메시지 수신 처리가 깔끔해요

로깅 후 showNotification으로 위임하는 구조가 명확하네요.


72-87: 채널 생성 로직이 개선되었어요

이전 리뷰에서 지적된 채널 재생성 문제가 해결되어 있네요. 이제 채널이 없을 때만 생성하도록 되어 있어요.


89-119: 알림 빌더 설정이 잘 되어 있어요

PendingIntent 플래그, 제목/본문 fallback, full-screen intent 등이 모두 적절하게 구성되어 있네요. 락스크린에서도 잘 보일 것 같아요.


120-131: 권한 체크 후 알림 표시가 안전하게 구현되어 있어요

POST_NOTIFICATIONS 권한을 확인하고, 없으면 로그를 남기는 방식이 좋아요. 126번 라인의 랜덤 ID는 각각 독립적인 알림을 표시하는 현재 목적에 적합하네요.

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

♻️ Duplicate comments (2)
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1)

75-75: 불필요한 빈 줄을 제거해주세요.

CI가 여기서 연속된 빈 줄이 있다고 지적하고 있네요. 함수 사이에 빈 줄 하나만 남기고 이 줄을 제거해주시면 됩니다.

다음 diff를 적용해주세요:

     )
 }
 
-
 @OptIn(ExperimentalFoundationApi::class)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

38-40: 블록 주석 포맷을 수정하세요 (Pipeline 실패)

주석의 별표 정렬이 잘못되어 CI에서 BlockCommentInitialStarAlignment 에러가 발생하고 있어요.

이렇게 수정해주세요:

-    /* FCM에서 해당 기기의 토큰 바꼈다고 알려줄 때 호출되는 콜백(앱 처음 설치, 앱데이터 삭제, FCM 내부 정책 변경 등)
-    FCM쪽에서 알아서 불러주는 콜백(개발자가 직접 호출 X,호출됐을 때 어떻게 저장할 지에 대한 코드)
-    * */
+    /*
+     * FCM에서 해당 기기의 토큰 바꼈다고 알려줄 때 호출되는 콜백(앱 처음 설치, 앱데이터 삭제, FCM 내부 정책 변경 등)
+     * FCM쪽에서 알아서 불러주는 콜백(개발자가 직접 호출 X,호출됐을 때 어떻게 저장할 지에 대한 코드)
+     */
🧹 Nitpick comments (2)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (2)

53-53: TODO 주석을 이슈로 트래킹하면 좋을 것 같아요

서버에 새 토큰을 알려주는 기능이 필요할 것 같은데, 나중에 잊지 않도록 이슈로 만들어두면 어떨까요?

원하시면 제가 이슈 생성을 도와드릴 수 있어요!


117-117: fullScreenIntent 사용을 재검토해보세요

setFullScreenIntent(contentPi, true)는 화면이 꺼져있을 때 전체화면으로 액티비티를 띄우는 기능이에요. 이건 알람이나 수신 전화 같은 매우 중요한 알림에만 사용하는게 일반적이에요. 일반 푸시 알림에 사용하면 사용자가 부담스러워할 수 있어요.

일반 알림이라면 이 줄을 제거하는 걸 고려해보세요:

         .setContentIntent(contentPi)
         .setAutoCancel(true)
-        .setFullScreenIntent(contentPi, true) // 화면 꺼져있을 때 팝업

정말 중요한 알림(예: 긴급 케어콜)에만 사용하고 싶다면, 알림 종류를 구분해서 특정 케이스에만 적용하는 방법도 있어요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66cd9e9 and 48ab848.

📒 Files selected for processing (3)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt
🧰 Additional context used
🪛 GitHub Actions: Android CI
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt

[error] 75-75: Detekt: NoConsecutiveBlankLines - Needless blank line(s).

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt

[error] 40-40: Detekt: BlockCommentInitialStarAlignment - Initial star should align with start of block comment.

🔇 Additional comments (2)
app/src/main/java/com/konkuk/medicarecall/ui/feature/homedetail/medicine/screen/MedicineDetail.kt (1)

76-135: 좋은 리팩토링이네요!

상태를 가진 MedicineDetail에서 순수한 프레젠테이션 레이어인 MedicineDetailLayout을 분리한 것은 좋은 접근이에요. 테스트 가능성도 높아지고 재사용성도 좋아집니다. 파라미터 구조도 깔끔하고 UI 구성도 잘 되어 있네요.

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

60-64: 깔끔한 구현이에요!

메시지 수신 처리가 명확하게 분리되어 있고, 로깅도 적절해요.

@Inject
lateinit var fcmRepository: FcmRepository

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
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

serviceScope 생명주기 관리 추가 필요

SupervisorJob을 사용한 건 좋은 개선이에요! 다만 서비스가 종료될 때 scope를 취소하지 않으면 메모리 누수가 발생할 수 있어요.

onDestroy()를 추가해서 scope를 정리해주세요:

override fun onDestroy() {
    serviceScope.cancel()
    super.onDestroy()
}
🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt around
line 36, the CoroutineScope created with SupervisorJob + Dispatchers.IO isn't
cancelled on service teardown which can cause leaks; add an override of
onDestroy() that calls serviceScope.cancel() and then calls super.onDestroy() to
properly clean up the scope when the service is destroyed.

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

♻️ Duplicate comments (1)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

36-36: serviceScope 생명주기 관리가 여전히 누락되어 있어요

이전 리뷰에서 지적된 내용인데, onDestroy()에서 serviceScope.cancel()을 호출해야 메모리 누수를 방지할 수 있어요.

다음과 같이 onDestroy()를 추가해주세요:

+    override fun onDestroy() {
+        serviceScope.cancel()
+        super.onDestroy()
+    }
🧹 Nitpick comments (1)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

55-55: 서버 토큰 동기화 구현이 필요하신가요?

주석에 서버에 토큰을 전송하는 로직이 필요하다고 언급되어 있는데, 이 부분 구현을 도와드릴 수 있어요.

필요하시면 API 호출 코드를 생성하거나, 이슈를 열어서 나중에 작업하도록 할 수 있어요!

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48ab848 and cad0504.

📒 Files selected for processing (1)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (2)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (2)

48-59: 에러 처리가 잘 되어 있네요!

try-catch로 토큰 저장 실패를 적절히 처리하고, 로깅도 명확하게 되어 있어서 디버깅하기 좋아 보여요.


122-131: 권한 체크 로직이 적절해요

알림을 표시하기 전에 POST_NOTIFICATIONS 권한을 확인하고, 권한이 없을 때는 로그를 남기는 방식이 좋아 보여요. 랜덤 notification ID 사용도 각 알림을 고유하게 표시하기에 적합해요.

.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(contentPi)
.setAutoCancel(true)
.setFullScreenIntent(contentPi, true) // 화면 꺼져있을 때 팝업
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Android 14+ 에서 전체 화면 인텐트 권한 확인이 필요해요

setFullScreenIntent를 사용하면 화면이 꺼져있을 때 알림을 팝업으로 표시할 수 있는데, Android 14(API 34) 이상에서는 USE_FULL_SCREEN_INTENT 권한이 필요해요.

다음 스크립트로 매니페스트에 권한이 선언되어 있는지 확인해주세요:

권한이 없다면 Android 14+ 기기에서 전체 화면 알림이 작동하지 않을 수 있어요.


🏁 Script executed:

#!/bin/bash
# AndroidManifest.xml에 USE_FULL_SCREEN_INTENT 권한이 있는지 확인

rg -n "USE_FULL_SCREEN_INTENT" app/src/main/AndroidManifest.xml

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find all AndroidManifest.xml files and check for USE_FULL_SCREEN_INTENT permission

echo "=== Finding AndroidManifest.xml files ==="
fd "AndroidManifest.xml" app/

echo -e "\n=== Checking all manifests for USE_FULL_SCREEN_INTENT ==="
fd "AndroidManifest.xml" app/ --exec cat {} \; | rg -n "USE_FULL_SCREEN_INTENT" 

echo -e "\n=== Checking build.gradle for targetSdkVersion ==="
fd "build.gradle.kts|build.gradle" app/ -x grep -n "targetSdk\|targetSdkVersion" {}

Length of output: 211


USE_FULL_SCREEN_INTENT 권한이 AndroidManifest.xml에 선언되어야 해요

코드에서 setFullScreenIntent()를 사용하고 있는데, 앱의 targetSdk가 36(Android 15)으로 설정되어 있네요. Android 12 이상에서는 USE_FULL_SCREEN_INTENT 권한을 반드시 선언해야 이 기능이 작동합니다.

현재 app/src/main/AndroidManifest.xml에 이 권한이 없으면, Android 12 이상의 기기에서 전체 화면 알림이 작동하지 않을 거예요.

해결 방법:

AndroidManifest.xml의 <manifest> 태그 내에 다음을 추가하세요:

<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt around
line 119 the code calls setFullScreenIntent(contentPi, true) but the required
permission USE_FULL_SCREEN_INTENT is not declared; add the permission
declaration to AndroidManifest.xml by inserting the uses-permission entry for
android.permission.USE_FULL_SCREEN_INTENT inside the <manifest> tag so
full-screen notifications work on Android 12+ (targetSdk 36).

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

♻️ Duplicate comments (1)
app/build.gradle.kts (1)

122-122: 중요: KSP 기반 Hilt Manager는 빌드 실패 야기 가능 (과거 리뷰 지적 사항)

AndroidX Hilt Compiler는 KAPT만 지원하므로, ksp(libs.hilt.manager)는 빌드를 깨뜨릴 확률이 높습니다. 불필요하면 제거하거나, 필요하면 KAPT로 전환하세요.

-    ksp(libs.hilt.manager)

WorkManager 등 AndroidX Hilt 확장을 사용하려면 KAPT 플러그인 추가 + kapt 의존성 전환이 필요합니다.

🧹 Nitpick comments (1)
app/build.gradle.kts (1)

84-86: 의존성 버전을 libs.versions.toml에 중앙화 권장

DataStore, Coroutines, Kotlin Serialization, Retrofit, OkHttp 등의 버전이 하드코딩되어 있습니다. 버전 관리 일관성을 위해 이들을 libs.versions.toml에 정의하고 libs.xxx 참조 방식으로 통일하는 것을 권장합니다.

-    implementation("androidx.datastore:datastore-preferences:1.1.1")
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
+    implementation(libs.androidx.datastore.preferences)
+    implementation(libs.kotlinx.coroutines.core)
+    implementation(libs.kotlinx.coroutines.android)

(libs.versions.toml에 해당 항목이 이미 정의되어 있다면 바로 적용 가능)

Also applies to: 98-98, 101-108

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cad0504 and 1ff751f.

📒 Files selected for processing (1)
  • app/build.gradle.kts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (3)
app/build.gradle.kts (3)

11-11: Google Services 플러그인 추가 적절

FCM 통합 구현에 필요한 Google Services 플러그인 추가로 Firebase 설정이 정상적으로 적용됩니다.


61-61: buildConfig 활성화 적절

BASE_URL을 buildConfigField로 주입하기 위해 필요한 설정으로, 정상적으로 추가되었습니다.


115-118: Firebase 의존성 추가 적절

Firebase BOM과 Cloud Messaging 라이브러리가 정상적으로 추가되어 FCM 통합 구현을 지원합니다.

@ProtossManse
Copy link
Collaborator

LGTM

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/src/main/java/com/konkuk/medicarecall/data/repository/MemberRegisterRepository.kt (1)

8-14: Trailing comma를 추가해주세요.

ktlint가 닫는 괄호 앞 trailing comma 누락을 감지했습니다.

다음 diff를 적용해서 수정하세요:

     suspend fun registerMember(
         token: String,
         name: String,
         birthDate: String,
         gender: GenderType,
-        fcmToken: String
+        fcmToken: String,
     ): Result<MemberTokenResponseDto>
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt (1)

28-34: 생성자 파라미터에 trailing comma 하나만 더 넣으면 ktlint 해결됩니다

LoginViewModel 생성자에 fcmRepository 추가한 건 좋습니다. 다만 멀티라인 파라미터 리스트라서 Line 33의 마지막 파라미터 뒤에 콤마가 없어서 TrailingCommaOnDeclarationSite 에러가 나고 있어요.

 class LoginViewModel @Inject constructor(
     private val verificationRepository: VerificationRepository,
     private val memberRegisterRepository: MemberRegisterRepository,
     private val dataStoreRepository: DataStoreRepository,
     private val checkLoginStatusUseCase: CheckLoginStatusUseCase,
-    private val fcmRepository: FcmRepository
+    private val fcmRepository: FcmRepository,
 ) : ViewModel() {
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/MemberRegisterRepositoryImpl.kt (1)

16-31: 회원가입에 fcmToken 전달은 잘 붙였고, trailing comma만 추가해 주시면 됩니다

registerMember 시그니처와 MemberRegisterRequestDto 생성자 모두 멀티라인이라, 마지막 인자 뒤에 콤마가 없어서 TrailingComma 관련 에러가 발생하고 있어요.

    override suspend fun registerMember(
        token: String,
        name: String,
        birthDate: String,
        gender: GenderType,
-        fcmToken: String
+        fcmToken: String,
    ): Result<MemberTokenResponseDto> =

    ...

                MemberRegisterRequestDto(
                    name,
                    birthDate,
                    gender,
-                    fcmToken
+                    fcmToken,
                ),

이렇게만 정리하면 이 파일의 ktlint 에러는 모두 사라질 것 같습니다.

♻️ Duplicate comments (2)
app/build.gradle.kts (1)

122-122: ⚠️ 중요: 이전 리뷰에서 지적된 KSP 호환성 문제 여전히 미해결

ksp(libs.hilt.manager)는 빌드를 실패시킬 가능성이 높습니다. androidx.hilt:hilt-compiler는 KAPT만 지원하며 KSP는 미지원합니다.

두 가지 해결책:

옵션 1 (불필요 시, 권장): 제거

-    ksp(libs.hilt.manager)

옵션 2 (필요 시): KAPT로 전환

plugins 블록에 추가:

id("org.jetbrains.kotlin.kapt")

그리고 라인 122를 수정:

-    ksp(libs.hilt.manager)
+    kapt(libs.hilt.manager)

WorkManager나 AndroidX Hilt 확장을 사용 중이라면 옵션 2를, 그렇지 않으면 libs.versions.toml에서 hilt-manager 항목을 삭제하는 것을 권장합니다.

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1)

41-67: serviceScope를 onDestroy에서 cancel하지 않으면 서비스 종료 후 코루틴이 남을 수 있어요

SupervisorJob을 사용한 IO 스코프는 좋은데, 현재는 서비스 생명주기와 연결되지 않아서 서비스가 종료되어도 작업이 계속 돌 수 있습니다. 기존 리뷰에서도 언급된 내용이라, 아래처럼 onDestroy에서 정리해 두는 게 안전합니다.

override fun onDestroy() {
    serviceScope.cancel()
    super.onDestroy()
}

이 메서드를 FcmService 안에 추가해 주세요.

🧹 Nitpick comments (3)
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmUpdateService.kt (1)

12-12: 요청 body에 Map 대신 DTO 사용을 고려해보세요.

현재 Map<String, String>를 사용하고 있는데, 간단한 payload지만 FcmTokenUpdateRequestDto 같은 전용 DTO를 만들면 타입 안전성이 높아지고 API 계약이 명확해집니다. 다른 DTO들과의 일관성도 좋아지구요.

DTO를 추가하려면:

// FcmTokenUpdateRequestDto.kt
data class FcmTokenUpdateRequestDto(
    val fcmToken: String,
)

그리고 서비스 인터페이스 수정:

-        @Body body: Map<String, String>, // {"fcmToken": "string"}
+        @Body body: FcmTokenUpdateRequestDto,
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (2)

52-66: FcmRepositoryImpl로 캐스팅하지 않고도 인터페이스로 바로 호출할 수 있어요

FcmRepository 인터페이스에 이미 validateAndRefreshTokenIfNeeded가 정의되어 있어서, 구현체로 캐스팅할 필요가 없고, 구현 교체에도 조금 더 유연해집니다.

-                // 서버 갱신 (jwtToken은 DataStore에서 가져오거나, 로그인 시 저장된 값을 사용)
-                val jwtToken = dataStoreRepository.getAccessToken() ?: return@launch
-                (fcmRepository as? FcmRepositoryImpl)?.validateAndRefreshTokenIfNeeded(jwtToken)
+                // 서버 갱신 (jwtToken은 DataStore에서 가져오거나, 로그인 시 저장된 값을 사용)
+                val jwtToken = dataStoreRepository.getAccessToken() ?: return@launch
+                fcmRepository.validateAndRefreshTokenIfNeeded(jwtToken)

이렇게 하면 import com.konkuk.medicarecall.data.repositoryimpl.FcmRepositoryImpl도 정리할 수 있습니다.


128-138: POST_NOTIFICATIONS 권한 체크는 Android 13+에서만 필요하니, SDK 버전 조건을 같이 쓰는 걸 추천해요

현재는 모든 버전에서 Manifest.permission.POST_NOTIFICATIONS를 체크하고 있는데, 일반적으로는 Android 13(API 33) 이상에서만 런타임 권한을 요구합니다. 보통은 아래처럼 버전 조건을 같이 두는 패턴을 많이 씁니다.

-        if (ActivityCompat.checkSelfPermission(
-                this,
-                Manifest.permission.POST_NOTIFICATIONS,
-            ) == PackageManager.PERMISSION_GRANTED
-        ) {
+        if (
+            Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
+            ActivityCompat.checkSelfPermission(
+                this,
+                Manifest.permission.POST_NOTIFICATIONS,
+            ) == PackageManager.PERMISSION_GRANTED
+        ) {
             val notificationId = (0..Int.MAX_VALUE).random()
             NotificationManagerCompat.from(this).notify(notificationId, builder.build())
         } else {
             Log.w(TAG, "POST_NOTIFICATIONS 권한이 없어 알림을 표시하지 못했습니다.")
         }

이렇게 하면 13 미만에서는 권한 체크 없이 바로 알림을 보내고, 13 이상에서만 실제 권한 상태를 보고 분기하게 됩니다. 실제 동작은 Android 버전별 문서를 한 번 더 확인해 주시면 좋겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff751f and fabaa14.

📒 Files selected for processing (17)
  • app/build.gradle.kts (3 hunks)
  • app/src/main/AndroidManifest.xml (3 hunks)
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt (4 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmUpdateService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmValidationService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/api/fcm/NotificationService.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/di/ApiModule.kt (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/dto/request/MemberRegisterRequestDto.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/dto/request/NotificationStatusRequestDto.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/model/FcmToken.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/FcmRepository.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/MemberRegisterRepository.kt (2 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repository/NotificationRepository.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1 hunks)
  • app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/MemberRegisterRepositoryImpl.kt (3 hunks)
  • app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/com/konkuk/medicarecall/data/model/FcmToken.kt
  • app/src/main/java/com/konkuk/medicarecall/MainActivity.kt
  • app/src/main/java/com/konkuk/medicarecall/data/repository/FcmRepository.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt (1)
app/src/main/java/com/konkuk/medicarecall/data/repository/FcmRepository.kt (3)
  • getFcmToken (5-5)
  • saveFcmToken (3-9)
  • saveFcmToken (4-4)
🪛 GitHub Actions: Android CI
app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmUpdateService.kt

[error] 12-12: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

app/src/main/java/com/konkuk/medicarecall/data/repository/NotificationRepository.kt

[error] 4-4: SpacingAroundColon. Unexpected spacing before ':'

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/NotificationService.kt

[error] 13-13: SpacingAroundColon. Unexpected spacing before ':'


[error] 14-14: FunctionReturnTypeSpacing. Unexpected spacing after function return type


[error] 14-14: SpacingAroundColon. Unexpected spacing before ':'


[error] 14-14: Indentation. Unexpected indentation (8) (should be 4)


[error] 13-13: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

app/src/main/java/com/konkuk/medicarecall/data/dto/request/NotificationStatusRequestDto.kt

[error] 4-4: SpacingAroundColon. Unexpected spacing before ':'


[error] 4-4: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

app/src/main/java/com/konkuk/medicarecall/data/repository/MemberRegisterRepository.kt

[error] 4-4: NoUnusedImports. Unused import.


[error] 13-13: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt

[error] 28-28: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'


[error] 78-78: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

app/src/main/java/com/konkuk/medicarecall/data/di/ApiModule.kt

[error] 152-152: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt

[error] 33-33: Trailing comma missing before ')'. (TrailingCommaOnDeclarationSite)


[error] 142-142: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/MemberRegisterRepositoryImpl.kt

[error] 6-6: NoUnusedImports. Unused import.


[error] 30-30: TrailingCommaOnCallSite. Missing trailing comma before ')'


[error] 21-21: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

🔇 Additional comments (11)
app/build.gradle.kts (6)

11-11: Google Services 플러그인 추가 — FCM 통합 준비 완료! 👍

Google Services 플러그인이 적절히 추가되었으니, Firebase BOM과 메시징 의존성을 활용할 수 있습니다. 좋은 구성입니다.


37-37: BASE_URL 설정 간소화 — 명확하고 깔끔해졌네요 ✨

Kotlin의 문자열 보간으로 한 줄로 통합했습니다. Elvis 연산자로 안전한 기본값도 처리되어 있습니다.


61-61: buildConfig 활성화 — 필수 설정입니다 ✓

BuildConfig 필드를 사용하므로 활성화가 필요했습니다. 올바르게 추가되었습니다.


83-86: DataStore & Coroutines 명시적 추가 — 토큰 저장 구조 완성 👍

SharedPreferences에서 DataStore로의 전환 목표와 정확히 맞춰졌습니다. FCM 토큰 암호화 저장을 위해 필요한 코루틴 버전도 일관되게 설정되어 있습니다.


115-118: Firebase 의존성 적절히 추가됨 🔥

BOM을 통한 버전 관리와 FCM 메시징 의존성이 올바르게 설정되었습니다. Analytics도 함께 추가되어 푸시 알림 추적에 유용할 것 같습니다.


108-108: 유의: Accompanist WebView가 RC(릴리스 후보) 버전입니다 ⚠️

accompanist-webview:0.24.13-rc는 아직 정식 버전이 아닙니다. 가능하면 안정화된 버전을 사용하는 걸 권장합니다. 필요한 특정 기능이 있다면 괜찮지만, 없다면 안정화 버전으로 업그레이드하세요.

app/src/main/AndroidManifest.xml (3)

9-9: 알림 권한 추가 LGTM!

Android 13(API 33) 이상에서 필수인 POST_NOTIFICATIONS 권한이 올바르게 추가되었습니다.


41-47: FCM 서비스 등록 LGTM!

FirebaseMessagingService가 올바르게 등록되었고, exported=false로 보안도 적절히 설정되었습니다.


27-29: FCM 채널 메타데이터 확인 완료!

AndroidManifest.xml의 fcm_alert 값이 App.kt의 FCM_CHANNEL_ID 상수와 정확히 일치합니다. 채널 ID가 일관되게 설정되어 있으므로 문제없습니다.

app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmValidationService.kt (1)

6-9: FCM 토큰 검증 서비스 LGTM!

간결하고 명확한 Retrofit 인터페이스입니다. 토큰 검증 엔드포인트가 잘 정의되어 있습니다.

app/src/main/java/com/konkuk/medicarecall/data/dto/request/MemberRegisterRequestDto.kt (1)

11-11: fcmToken 필드 적절히 전달 완료

검증 결과, fcmToken이 전체 레이어에서 올바르게 전달되고 있습니다:

  • Repository 인터페이스 및 구현에 fcmToken 파라미터 포함 ✓
  • ViewModel에서 Firebase로부터 토큰을 조회하여 registerMember() 호출 시 전달 ✓
  • DTO에 정의된 fcmToken 필드로 최종 전달 ✓

회원 가입 플로우에서 FCM 토큰이 모든 계층을 통해 일관되게 전달되고 있습니다.

Comment on lines +10 to +13
suspend fun updateFcmToken(
@Header("Authorization") header: String? = null, // Optional header
@Body body: Map<String, String> // {"fcmToken": "string"}
): Response<Unit>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Trailing comma를 추가해주세요.

ktlint가 닫는 괄호 앞 trailing comma 누락을 감지했습니다.

다음 diff를 적용해서 수정하세요:

     suspend fun updateFcmToken(
         @Header("Authorization") header: String? = null, // Optional header
-        @Body body: Map<String, String> // {"fcmToken": "string"}
+        @Body body: Map<String, String>, // {"fcmToken": "string"}
     ): Response<Unit>
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 12-12: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/fcm/FcmUpdateService.kt
around lines 10 to 13, ktlint flagged a missing trailing comma before the
closing parenthesis of the updateFcmToken function parameter list; add a
trailing comma after the last parameter (the @Body body: Map<String, String>
entry) so the parameter list ends with a comma before the closing parenthesis to
satisfy ktlint formatting rules.

Comment on lines +11 to +14
suspend fun changeStatus(
@Path("notificationId") notificationId: String,
@Body status : NotificationStatusRequestDto
) : Response<Unit>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

여러 코드 스타일 이슈를 수정해주세요.

ktlint가 여러 스타일 위반을 감지했습니다:

  • 콜론 앞 불필요한 공백
  • 리턴 타입 앞 불필요한 공백
  • 닫는 괄호 들여쓰기 오류
  • trailing comma 누락

다음 diff를 적용해서 수정하세요:

     @POST("notifications/{notificationId}")
     suspend fun changeStatus(
         @Path("notificationId") notificationId: String,
-        @Body status : NotificationStatusRequestDto
-        ) : Response<Unit>
+        @Body status: NotificationStatusRequestDto,
+    ): Response<Unit>
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
suspend fun changeStatus(
@Path("notificationId") notificationId: String,
@Body status : NotificationStatusRequestDto
) : Response<Unit>
suspend fun changeStatus(
@Path("notificationId") notificationId: String,
@Body status: NotificationStatusRequestDto,
): Response<Unit>
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 13-13: SpacingAroundColon. Unexpected spacing before ':'


[error] 14-14: FunctionReturnTypeSpacing. Unexpected spacing after function return type


[error] 14-14: SpacingAroundColon. Unexpected spacing before ':'


[error] 14-14: Indentation. Unexpected indentation (8) (should be 4)


[error] 13-13: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/api/fcm/NotificationService.kt
around lines 11 to 14, fix ktlint style issues in the suspend fun signature:
remove the unnecessary space before colons so parameters read "status:
NotificationStatusRequestDto" and the return type reads ": Response<Unit>",
ensure the closing parenthesis is indented/aligned with the function start
(place it on its own line if multiline), and add a trailing comma after the last
parameter in the multiline parameter list so the signature conforms to ktlint
rules.

Comment on lines +141 to 153
@Provides
@Singleton
fun provideFcmValidationService(retrofit: Retrofit): FcmValidationService {
return retrofit.create(FcmValidationService::class.java)
}

@Provides
@Singleton
fun provideFcmUpdateService(retrofit: Retrofit): FcmUpdateService {
return retrofit.create(FcmUpdateService::class.java)
}

}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

FCM Retrofit 서비스 DI 추가는 OK인데, 마지막 공백 줄 때문에 ktlint가 깨지고 있어요

provideFcmValidationService, provideFcmUpdateService 구현은 기존 패턴이랑 잘 맞습니다. 다만 Line 152의 빈 줄 때문에 NoBlankLineBeforeRbrace 에러가 나고 있어서, 닫는 중괄호 바로 앞 공백만 지워주면 CI 통과할 것 같아요.

     fun provideFcmUpdateService(retrofit: Retrofit): FcmUpdateService {
         return retrofit.create(FcmUpdateService::class.java)
     }
-
-}
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Provides
@Singleton
fun provideFcmValidationService(retrofit: Retrofit): FcmValidationService {
return retrofit.create(FcmValidationService::class.java)
}
@Provides
@Singleton
fun provideFcmUpdateService(retrofit: Retrofit): FcmUpdateService {
return retrofit.create(FcmUpdateService::class.java)
}
}
@Provides
@Singleton
fun provideFcmValidationService(retrofit: Retrofit): FcmValidationService {
return retrofit.create(FcmValidationService::class.java)
}
@Provides
@Singleton
fun provideFcmUpdateService(retrofit: Retrofit): FcmUpdateService {
return retrofit.create(FcmUpdateService::class.java)
}
}
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 152-152: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

🤖 Prompt for AI Agents
In app/src/main/java/com/konkuk/medicarecall/data/di/ApiModule.kt around lines
141 to 153, there's an extra blank line immediately before the closing brace
causing a ktlint NoBlankLineBeforeRbrace error; remove the empty line so the
closing '}' directly follows the previous method block without any intervening
blank line to satisfy the lint rule.

Comment on lines +3 to +5
data class NotificationStatusRequestDto(
val read : Boolean
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

코드 스타일 이슈를 수정해주세요.

ktlint가 두 가지 스타일 이슈를 발견했습니다:

  • 콜론 앞 불필요한 공백
  • 닫는 괄호 앞 trailing comma 누락

다음 diff를 적용해서 수정하세요:

 data class NotificationStatusRequestDto(
-    val read : Boolean
+    val read: Boolean,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data class NotificationStatusRequestDto(
val read : Boolean
)
data class NotificationStatusRequestDto(
val read: Boolean,
)
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 4-4: SpacingAroundColon. Unexpected spacing before ':'


[error] 4-4: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/data/dto/request/NotificationStatusRequestDto.kt
around lines 3 to 5, ktlint reports two style issues: remove the unnecessary
space before the colon in the property declaration (change "val read : Boolean"
to "val read: Boolean") and add a trailing comma before the closing parenthesis
of the data class parameter list; apply those two changes and save the file.

package com.konkuk.medicarecall.data.repository

import com.konkuk.medicarecall.data.dto.response.MemberTokenResponseDto
import com.konkuk.medicarecall.data.model.FcmToken
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

사용하지 않는 import를 제거해주세요.

FcmToken import가 사용되지 않습니다. ktlint가 이를 감지했습니다.

다음 diff를 적용해서 수정하세요:

 package com.konkuk.medicarecall.data.repository
 
 import com.konkuk.medicarecall.data.dto.response.MemberTokenResponseDto
-import com.konkuk.medicarecall.data.model.FcmToken
 import com.konkuk.medicarecall.ui.type.GenderType
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 4-4: NoUnusedImports. Unused import.

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/data/repository/MemberRegisterRepository.kt
around line 4, the import "com.konkuk.medicarecall.data.model.FcmToken" is
unused; remove that import line from the file (and ensure there are no remaining
references to FcmToken in this file), then re-run ktlint/gradle checks to
confirm the warning is resolved.

Comment on lines +24 to +29
@Singleton
class FcmRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
private val fcmValidationService: FcmValidationService,
private val fcmUpdateService: FcmUpdateService
) : FcmRepository {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

생성자 마지막 파라미터 뒤에 콤마 하나만 추가하면 ktlint 에러가 사라집니다

FcmRepositoryImpl 생성자 파라미터가 멀티라인이라 fcmUpdateService 뒤에 trailing comma가 필요합니다 (TrailingCommaOnDeclarationSite).

 @Singleton
 class FcmRepositoryImpl @Inject constructor(
     @ApplicationContext private val context: Context,
     private val fcmValidationService: FcmValidationService,
-    private val fcmUpdateService: FcmUpdateService
+    private val fcmUpdateService: FcmUpdateService,
 ) : FcmRepository {
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 28-28: TrailingCommaOnDeclarationSite. Missing trailing comma before ')'

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt
around lines 24 to 29, the multiline constructor parameter list is missing a
trailing comma after the last parameter (`fcmUpdateService`), which triggers
ktlint's TrailingCommaOnDeclarationSite rule; add a comma after
`fcmUpdateService` (i.e., make the final constructor parameter line end with a
comma) so the declaration conforms to ktlint and formats correctly.

Comment on lines +39 to +84
override suspend fun clearToken() {
context.fcmDataStore.updateData {
FcmToken(null)
}
}

override suspend fun validateAndRefreshTokenIfNeeded(jwtToken: String) {
try {
val currentToken = getFcmToken()
Log.d("FcmRepositoryImpl", "현재 저장된 FCM 토큰: $currentToken")

// 서버에 토큰 유효성 검사 요청
val isValid = runCatching {
fcmValidationService.validateToken().isSuccessful
}.getOrDefault(false)

if (isValid) {
Log.d("FcmRepositoryImpl", "FCM 토큰 유효함, 갱신 불필요")
return
}

// 유효하지 않으면 Firebase로 새 토큰 발급
val newToken = FirebaseMessaging.getInstance().token.await()
saveFcmToken(newToken)
Log.d("FcmRepositoryImpl", "새 FCM 토큰 발급 및 저장 완료: $newToken")

// 서버에 갱신 요청
val response = runCatching {
fcmUpdateService.updateFcmToken(
header = "Bearer $jwtToken",
body = mapOf("fcmToken" to newToken),
)
}

response.onSuccess {
Log.d("FcmRepositoryImpl", "서버에 FCM 토큰 갱신 성공")
}.onFailure {
Log.e("FcmRepositoryImpl", "서버에 토큰 갱신 실패: ${it.message}")
}

} catch (e: Exception) {
Log.e("FcmRepositoryImpl", "validateAndRefreshTokenIfNeeded 실패: ${e.message}")
}
}

}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

토큰 검증/갱신 로직은 좋고, 마지막 공백 줄만 정리하면 될 것 같아요

  • validateAndRefreshTokenIfNeeded의 흐름(검증 → 필요 시 Firebase에서 새 토큰 → DataStore 저장 → 서버 갱신)은 괜찮습니다.
  • Line 83의 빈 줄 때문에 NoBlankLineBeforeRbrace 에러가 나고 있으니, 닫는 중괄호 바로 앞 공백만 제거해 주세요.
         } catch (e: Exception) {
             Log.e("FcmRepositoryImpl", "validateAndRefreshTokenIfNeeded 실패: ${e.message}")
         }
-    }
-
-}
+    }
+}

참고로 currentToken은 지금 로그에만 쓰이고 있으니, 나중에 필요 없으면 변수 자체를 없애도 됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override suspend fun clearToken() {
context.fcmDataStore.updateData {
FcmToken(null)
}
}
override suspend fun validateAndRefreshTokenIfNeeded(jwtToken: String) {
try {
val currentToken = getFcmToken()
Log.d("FcmRepositoryImpl", "현재 저장된 FCM 토큰: $currentToken")
// 서버에 토큰 유효성 검사 요청
val isValid = runCatching {
fcmValidationService.validateToken().isSuccessful
}.getOrDefault(false)
if (isValid) {
Log.d("FcmRepositoryImpl", "FCM 토큰 유효함, 갱신 불필요")
return
}
// 유효하지 않으면 Firebase로 새 토큰 발급
val newToken = FirebaseMessaging.getInstance().token.await()
saveFcmToken(newToken)
Log.d("FcmRepositoryImpl", "새 FCM 토큰 발급 및 저장 완료: $newToken")
// 서버에 갱신 요청
val response = runCatching {
fcmUpdateService.updateFcmToken(
header = "Bearer $jwtToken",
body = mapOf("fcmToken" to newToken),
)
}
response.onSuccess {
Log.d("FcmRepositoryImpl", "서버에 FCM 토큰 갱신 성공")
}.onFailure {
Log.e("FcmRepositoryImpl", "서버에 토큰 갱신 실패: ${it.message}")
}
} catch (e: Exception) {
Log.e("FcmRepositoryImpl", "validateAndRefreshTokenIfNeeded 실패: ${e.message}")
}
}
}
override suspend fun clearToken() {
context.fcmDataStore.updateData {
FcmToken(null)
}
}
override suspend fun validateAndRefreshTokenIfNeeded(jwtToken: String) {
try {
val currentToken = getFcmToken()
Log.d("FcmRepositoryImpl", "현재 저장된 FCM 토큰: $currentToken")
// 서버에 토큰 유효성 검사 요청
val isValid = runCatching {
fcmValidationService.validateToken().isSuccessful
}.getOrDefault(false)
if (isValid) {
Log.d("FcmRepositoryImpl", "FCM 토큰 유효함, 갱신 불필요")
return
}
// 유효하지 않으면 Firebase로 새 토큰 발급
val newToken = FirebaseMessaging.getInstance().token.await()
saveFcmToken(newToken)
Log.d("FcmRepositoryImpl", "새 FCM 토큰 발급 및 저장 완료: $newToken")
// 서버에 갱신 요청
val response = runCatching {
fcmUpdateService.updateFcmToken(
header = "Bearer $jwtToken",
body = mapOf("fcmToken" to newToken),
)
}
response.onSuccess {
Log.d("FcmRepositoryImpl", "서버에 FCM 토큰 갱신 성공")
}.onFailure {
Log.e("FcmRepositoryImpl", "서버에 토큰 갱신 실패: ${it.message}")
}
} catch (e: Exception) {
Log.e("FcmRepositoryImpl", "validateAndRefreshTokenIfNeeded 실패: ${e.message}")
}
}
}
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 78-78: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/FcmRepositoryImpl.kt
around lines 39 to 84, remove the extra blank line immediately before the
closing brace at line 83 to fix the NoBlankLineBeforeRbrace error; ensure the
file ends with the closing brace directly after the previous line (no blank
line), and optionally remove the now-unused currentToken variable and its Log
statement if you decide it’s unnecessary.

Comment on lines 3 to 8
import com.konkuk.medicarecall.data.api.member.MemberRegisterService
import com.konkuk.medicarecall.data.dto.request.MemberRegisterRequestDto
import com.konkuk.medicarecall.data.dto.response.MemberTokenResponseDto
import com.konkuk.medicarecall.data.repository.FcmRepository
import com.konkuk.medicarecall.data.repository.MemberRegisterRepository
import com.konkuk.medicarecall.ui.type.GenderType
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용되지 않는 FcmRepository import 때문에 lint가 실패하고 있어요

Line 6의 import com.konkuk.medicarecall.data.repository.FcmRepository는 이 파일에서 실제로 사용되지 않아서 NoUnusedImports 에러가 나는 상태입니다. 안전하게 삭제해도 될 것 같아요.

-import com.konkuk.medicarecall.data.repository.FcmRepository
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import com.konkuk.medicarecall.data.api.member.MemberRegisterService
import com.konkuk.medicarecall.data.dto.request.MemberRegisterRequestDto
import com.konkuk.medicarecall.data.dto.response.MemberTokenResponseDto
import com.konkuk.medicarecall.data.repository.FcmRepository
import com.konkuk.medicarecall.data.repository.MemberRegisterRepository
import com.konkuk.medicarecall.ui.type.GenderType
import com.konkuk.medicarecall.data.api.member.MemberRegisterService
import com.konkuk.medicarecall.data.dto.request.MemberRegisterRequestDto
import com.konkuk.medicarecall.data.dto.response.MemberTokenResponseDto
import com.konkuk.medicarecall.data.repository.MemberRegisterRepository
import com.konkuk.medicarecall.ui.type.GenderType
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 6-6: NoUnusedImports. Unused import.

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/data/repositoryimpl/MemberRegisterRepositoryImpl.kt
around lines 3 to 8, the import for
com.konkuk.medicarecall.data.repository.FcmRepository is unused and causing a
NoUnusedImports lint error; remove that import line and save the file (then run
the linter or Gradle build to confirm the error is resolved).

Comment on lines +78 to 97
/** 인증번호 확인 및 로그인 처리 */
fun confirmPhoneNumber(phone: String, code: String) {
viewModelScope.launch {
if (!debug) {
verificationRepository.confirmPhoneNumber(phone, code)
.onSuccess {
Log.d(
"httplog",
"${it.message} ${it.memberStatus} 액세스토큰: ${it.accessToken} 리프레시 토큰: ${it.refreshToken} ${it.verified} ${it.token} ",
)
Log.d("httplog", "로그인 성공, 액세스토큰: ${it.accessToken}")

isVerified = it.verified
if (isVerified) {
token = it.token ?: ""
dataStoreRepository.saveAccessToken(it.accessToken ?: "")
dataStoreRepository.saveRefreshToken(it.refreshToken ?: "")

// 로그인 후 FCM 토큰 유효성 검사
it.accessToken?.let { jwt ->
fcmRepository.validateAndRefreshTokenIfNeeded(jwt)
Log.d("httplog", "FCM 토큰 유효성 검사 및 갱신 완료")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

validateAndRefreshTokenIfNeeded는 suspend라, 현재 위치에서 바로 호출하면 컴파일 이슈가 날 수 있어요

FcmRepository 인터페이스에서 validateAndRefreshTokenIfNeededsuspend로 정의되어 있는데, 지금은 Result.onSuccess { ... } 블록 안에서 그냥 호출하고 있어서 컴파일 에러가 발생할 가능성이 큽니다.

viewModelScope.launch 안이긴 하지만, onSuccess의 람다는 일반 함수 타입이라서 suspend 호출을 직접 넣을 수 없어요. 아래처럼 내부에서 한 번 더 코루틴을 띄우는 식으로 옮기는 게 안전합니다.

-                            // 로그인 후 FCM 토큰 유효성 검사
-                            it.accessToken?.let { jwt ->
-                                fcmRepository.validateAndRefreshTokenIfNeeded(jwt)
-                                Log.d("httplog", "FCM 토큰 유효성 검사 및 갱신 완료")
-                            }
+                            // 로그인 후 FCM 토큰 유효성 검사
+                            it.accessToken?.let { jwt ->
+                                viewModelScope.launch {
+                                    fcmRepository.validateAndRefreshTokenIfNeeded(jwt)
+                                    Log.d("httplog", "FCM 토큰 유효성 검사 및 갱신 완료")
+                                }
+                            }

이렇게 하면 suspend 호출이 코루틴 블록 안으로 들어가서 컴파일도 되고, 구조도 명확해집니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** 인증번호 확인 및 로그인 처리 */
fun confirmPhoneNumber(phone: String, code: String) {
viewModelScope.launch {
if (!debug) {
verificationRepository.confirmPhoneNumber(phone, code)
.onSuccess {
Log.d(
"httplog",
"${it.message} ${it.memberStatus} 액세스토큰: ${it.accessToken} 리프레시 토큰: ${it.refreshToken} ${it.verified} ${it.token} ",
)
Log.d("httplog", "로그인 성공, 액세스토큰: ${it.accessToken}")
isVerified = it.verified
if (isVerified) {
token = it.token ?: ""
dataStoreRepository.saveAccessToken(it.accessToken ?: "")
dataStoreRepository.saveRefreshToken(it.refreshToken ?: "")
// 로그인 후 FCM 토큰 유효성 검사
it.accessToken?.let { jwt ->
fcmRepository.validateAndRefreshTokenIfNeeded(jwt)
Log.d("httplog", "FCM 토큰 유효성 검사 및 갱신 완료")
}
}
/** 인증번호 확인 및 로그인 처리 */
fun confirmPhoneNumber(phone: String, code: String) {
viewModelScope.launch {
if (!debug) {
verificationRepository.confirmPhoneNumber(phone, code)
.onSuccess {
Log.d("httplog", "로그인 성공, 액세스토큰: ${it.accessToken}")
isVerified = it.verified
if (isVerified) {
token = it.token ?: ""
dataStoreRepository.saveAccessToken(it.accessToken ?: "")
dataStoreRepository.saveRefreshToken(it.refreshToken ?: "")
// 로그인 후 FCM 토큰 유효성 검사
it.accessToken?.let { jwt ->
viewModelScope.launch {
fcmRepository.validateAndRefreshTokenIfNeeded(jwt)
Log.d("httplog", "FCM 토큰 유효성 검사 및 갱신 완료")
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt
around lines 78 to 97, the call to
fcmRepository.validateAndRefreshTokenIfNeeded(jwt) is a suspend function but is
being invoked directly inside Result.onSuccess { ... } (a non-suspend lambda),
which will cause a compile error; fix it by moving the suspend call into a
coroutine scope (e.g., call viewModelScope.launch {
fcmRepository.validateAndRefreshTokenIfNeeded(jwt) } or use
lifecycleScope/withContext as appropriate), ensuring you pass the jwt captured
from it.accessToken and keep the surrounding logic (logging) after the coroutine
completes or move the log inside the launched coroutine.

Comment on lines +114 to 148
/** 회원가입 */
fun memberRegister(name: String, birthDate: String, gender: GenderType) {
viewModelScope.launch {
if (!debug) {
try {
if (debug) {
_events.emit(LoginEvent.MemberRegisterSuccess)
return@launch
}

// 🔹 이미 로그인 단계에서 FCM 토큰은 유효성 검증 완료됨
val fcmToken = FirebaseMessaging.getInstance().token.await()
Log.d("httplog", "회원가입 시 FCM 토큰 사용: $fcmToken")

memberRegisterRepository.registerMember(
token,
name,
birthDate.formatAsDate(),
gender,
)
.onSuccess {
// 성공 로직
// responseDto에는 MemberRegisterResponseDto 객체가 들어있음
// 예: 성공 메시지 표시, 다음 화면으로 이동
Log.d("httplog", "성공, ${it.accessToken} ${it.refreshToken}")
dataStoreRepository.saveAccessToken(it.accessToken)
dataStoreRepository.saveRefreshToken(it.refreshToken)
_events.emit(LoginEvent.MemberRegisterSuccess)
}
.onFailure { exception ->
// 실패 로직
// exception에는 API 호출 중 발생한 예외가 들어있음
// 예: 에러 메시지 표시
Log.e("httplog", "회원가입 실패: ${exception.message}")
_events.emit(LoginEvent.MemberRegisterFailure)
}
} else {
_events.emit(LoginEvent.MemberRegisterSuccess)
token = token,
name = name,
birthDate = birthDate.formatAsDate(),
gender = gender,
fcmToken = fcmToken,
).onSuccess {
Log.d("httplog", "회원가입 성공: ${it.accessToken} ${it.refreshToken}")
dataStoreRepository.saveAccessToken(it.accessToken)
dataStoreRepository.saveRefreshToken(it.refreshToken)
_events.emit(LoginEvent.MemberRegisterSuccess)
}.onFailure { e ->
Log.e("httplog", "회원가입 실패: ${e.message}")
_events.emit(LoginEvent.MemberRegisterFailure)
}

} catch (e: Exception) {
Log.e("httplog", "회원가입 중 예외 발생: ${e.message}")
_events.emit(LoginEvent.MemberRegisterFailure)
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

회원가입 블록은 전체적으로 괜찮지만, 빈 줄 하나와 FCM 토큰 취득 위치를 살짝 정리하면 좋아 보여요

  1. Line 142의 빈 줄 때문에 NoBlankLineBeforeRbrace 에러가 나고 있습니다. } catch 사이의 공백만 정리해 주세요.
                 }.onFailure { e ->
                     Log.e("httplog", "회원가입 실패: ${e.message}")
                     _events.emit(LoginEvent.MemberRegisterFailure)
                 }
-
-            } catch (e: Exception) {
+            } catch (e: Exception) {
  1. FirebaseMessaging.getInstance().token.await()를 ViewModel에서 직접 호출하는 대신, 나중에는 FcmRepository를 통해 토큰을 얻는 방향으로 정리하면 FCM 관련 처리가 한 레이어에 모여서 유지보수가 좀 더 편해질 것 같습니다. (지금 구현도 동작상 문제는 없어 보이니, 추후 리팩터 용도로만 고려하셔도 됩니다.)
🧰 Tools
🪛 GitHub Actions: Android CI

[error] 142-142: NoBlankLineBeforeRbrace. Unexpected blank line(s) before closing brace.

🤖 Prompt for AI Agents
In
app/src/main/java/com/konkuk/medicarecall/ui/feature/login/info/viewmodel/LoginViewModel.kt
around lines 114–148, remove the extra blank line before the catch block to
satisfy NoBlankLineBeforeRbrace (i.e., eliminate the empty line between the
closing brace of the try block and the catch), and instead of calling
FirebaseMessaging.getInstance().token.await() directly in the ViewModel, inject
or obtain an FcmRepository and call a suspending method like
fcmRepository.getToken() so FCM token retrieval is centralized in the repository
layer (update constructor/DI to provide FcmRepository, replace direct call with
repository call and preserve logging and error handling).

@alswlekk alswlekk merged commit 649a009 into develop Nov 18, 2025
1 check failed
@alswlekk alswlekk deleted the feat/fcm-alarm-#110 branch November 18, 2025 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 알림 FCM 관련 코드 등록

5 participants