Skip to content

Feat/#83 업데이트 전략 구현#84

Merged
DongChyeon merged 12 commits intodevelopfrom
feature/#83-update-popup
Apr 2, 2026
Merged

Feat/#83 업데이트 전략 구현#84
DongChyeon merged 12 commits intodevelopfrom
feature/#83-update-popup

Conversation

@DongChyeon
Copy link
Copy Markdown
Member

@DongChyeon DongChyeon commented Apr 2, 2026

🛠 Related issue

closed #83

어떤 변경사항이 있었나요?

  • 🐞 BugFix Something isn't working
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (Junit, etc.)

✅ CheckPoint

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • PR 컨벤션에 맞게 작성했습니다. (필수)
  • merge할 브랜치의 위치를 확인해 주세요(main❌/develop⭕) (필수)
  • Approve된 PR은 assigner가 머지하고, 수정 요청이 온 경우 수정 후 다시 push를 합니다. (필수)
  • BugFix의 경우, 버그의 원인을 파악하였습니다. (선택)

✏️ Work Description

항목 강제 업데이트 (Force) 소프트 업데이트 (Soft)
스크린샷 강제 업데이트 소프트 업데이트
타이틀 업데이트가 필요해요 새로운 버전이 있어요
본문 문구 앱을 계속 사용하려면 최신 버전으로
업데이트해 주세요.
더 나은 경험을 위해 업데이트를
권장합니다.
왼쪽 버튼 종료 (앱 종료) 나중에 (팝업 닫기)
오른쪽 버튼 업데이트 (스토어 이동) 업데이트 (스토어 이동)
제어 특징 취소 불가 (Back 버튼 등 차단) 사용자 선택 가능 (24시간 제어)

Firebase Remote Config를 통해 강제/소프트 업데이트 팝업을 제어하는 기능을 추가했습니다.

동작 방식

  • 스플래시 화면에서 토큰 체크와 Remote Config fetch를 병렬로 실행
  • 업데이트 팝업이 표시 중이면 홈/로그인 화면으로 이동하지 않음 (팝업 닫힌 후 네비게이션 진행)

Remote Config 파라미터

타입 설명
android_minimum_version Number 지원 최소 버전. 현재 버전이 낮으면 strategy 무관하게 강제 업데이트
android_latest_version Number 최신 버전. SOFT 전략에서 비교 기준
android_update_strategy String NONE / SOFT / FORCE

팝업 종류

  • 강제 업데이트: "업데이트" (스토어 이동) / "종료" (앱 종료), 바깥 터치 무시
  • 소프트 업데이트: "업데이트" (스토어 이동) / "나중에" (팝업 닫기), 하루 1회만 노출

주요 변경 파일

  • domain/model/AppUpdateInfo.kt — 업데이트 도메인 모델 및 UpdateStrategy enum 추가
  • domain/repository/AppUpdateRepository.kt — Repository 인터페이스 추가
  • core/data/datasource/RemoteConfigDataSourceImpl.kt — Firebase Remote Config fetch 구현
  • core/datastore/AppPreferencesDataSource.kt — 소프트 업데이트 마지막 노출 시각 저장 추가
  • feature/auth/ui/SplashViewModel.kt — 업데이트 체크 로직 추가, 팝업 닫힐 때까지 네비게이션 차단
  • feature/auth/ui/SplashScreen.kt — 업데이트 다이얼로그 UI 추가
  • feature/auth/ui/SplashContract.ktUpdateDialogType, DismissSoftUpdate Intent 추가

😅 Uncompleted Tasks

  • N/A

📢 To Reviewers

  • SplashViewModel에서 uiState.first { it.updateDialogType == UpdateDialogType.None } 으로 네비게이션을 차단합니다. 강제 업데이트의 경우 앱이 종료되기 전까지 이 suspend 함수가 계속 대기하는 구조입니다.
  • Remote Config fetch 실패(네트워크 없음 등) 시 runCatching으로 null을 반환하여 팝업 미표시 후 정상 진행합니다.
  • 소프트 업데이트 노출 빈도는 AppPreferencesDataStore에 마지막 표시 시각을 저장해 24시간 제한을 구현했습니다.
  • Firebase Console에서 Remote Config 파라미터 설정이 필요합니다. (설정 전까지는 기본값 NONE 적용)

📃 RCA 룰

  • R: 꼭 반영해 주세요. 적극적으로 고려해 주세요. (Request changes)
  • C: 웬만하면 반영해 주세요. (Comment)
  • A: 반영해도 좋고 넘어가도 좋습니다. 그냥 사소한 의견입니다. (Approve)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 앱 업데이트 확인 및 알림 기능 추가
    • 필수 업데이트와 선택적 업데이트 두 가지 유형 지원
    • 선택적 업데이트 알림을 무시할 수 있으며, 무시 시간이 저장됨
  • 개선 사항

    • 앱 종료 동작 개선
  • 기타

    • 내부 설명서 파일 정리

DongChyeon and others added 7 commits April 2, 2026 19:30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SplashViewModel에서 토큰 체크 + Remote Config fetch 병렬 실행
- 팝업이 닫히기 전까지 네비게이션 차단 (uiState.first)
- BuyOrNotViewModel에서 업데이트 로직 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@DongChyeon DongChyeon requested a review from Imagine-Choi April 2, 2026 11:13
@DongChyeon DongChyeon self-assigned this Apr 2, 2026
@DongChyeon DongChyeon added ✨ FEAT 기능 개발 (애매하면 기능 개발로 두도록 하자) 💪 동현동현동현 labels Apr 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Walkthrough

앱 업데이트 전략을 Firebase RemoteConfig로 구현하며, 스플래시 화면에 소프트/강제 업데이트 다이얼로그를 추가합니다. 네비게이션 계층 전반에 onFinish 콜백을 전달하여 강제 업데이트 종료를 지원합니다.

Changes

Cohort / File(s) Summary
UI/네비게이션 레이어 콜백 확장
app/src/main/java/com/sseotdabwa/buyornot/MainActivity.kt, app/src/main/java/com/sseotdabwa/buyornot/ui/BuyOrNotApp.kt, app/src/main/java/com/sseotdabwa/buyornot/navigation/BuyOrNotNavHost.kt
onFinish: () -> Unit 콜백 파라미터를 추가하여 강제 업데이트 시 앱 종료 동작을 트리거합니다.
스플래시 화면 업데이트 다이얼로그 구현
feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashContract.kt, feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashScreen.kt
UpdateDialogTypeDismissSoftUpdate 인텐트를 추가하고, 소프트/강제 업데이트 다이얼로그를 조건부로 렌더링하며 Play Store 오픈 기능을 구현합니다.
스플래시 네비게이션 및 ViewModel 업데이트
feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/navigation/AuthNavigation.kt, feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt
onFinish 콜백을 네비게이션에 추가하고, ViewModel에서 앱 업데이트 정보 조회 및 24시간 소프트 업데이트 억제 로직을 구현합니다.
도메인 레이어 모델 및 저장소
domain/src/main/java/com/sseotdabwa/buyornot/domain/model/AppUpdateInfo.kt, domain/src/main/java/com/sseotdabwa/buyornot/domain/repository/AppUpdateRepository.kt, domain/src/main/java/com/sseotdabwa/buyornot/domain/repository/AppPreferencesRepository.kt
AppUpdateInfo 데이터 클래스와 UpdateStrategy 열거형을 정의하고, AppUpdateRepository 인터페이스 및 lastSoftUpdateShownTime 속성을 추가합니다.
데이터 레이어 구현
core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSource.kt, core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSourceImpl.kt, core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppUpdateRepositoryImpl.kt
RemoteConfig 데이터 소스 인터페이스 및 구현을 추가하여 최신/최소 버전, 업데이트 전략을 Firebase RemoteConfig에서 조회합니다.
의존성 주입 및 환경설정
core/data/src/main/java/com/sseotdabwa/buyornot/core/data/di/DataModule.kt, core/data/src/main/java/com/sseotdabwa/buyornot/core/data/di/RemoteConfigModule.kt, core/data/build.gradle.kts, gradle/libs.versions.toml
RemoteConfigModule Hilt 모듈을 추가하고 Firebase RemoteConfig 및 coroutines-play-services 의존성을 설정합니다.
데이터스토어 업데이트
core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSource.kt, core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSourceImpl.kt, core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppPreferencesRepositoryImpl.kt
lastSoftUpdateShownTime: Flow<Long> 속성과 업데이트 메서드를 추가하여 마지막 소프트 업데이트 다이얼로그 표시 시각을 추적합니다.
포맷팅 및 구조 개선
core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/UserPreferencesDataSourceImpl.kt, core/network/src/main/java/com/sseotdabwa/buyornot/core/network/AuthEventBus.kt
생성자 선언을 단일 라인 형식으로 리포맷하고 클래스 멤버 들여쓰기 구조를 정리합니다. (기능 변경 없음)
문서 삭제
docs/CommitConvention.md, docs/Modularization.md, docs/SnackbarStateManagement.md
커밋 규칙, 모듈화 아키텍처, 스낵바 상태 관리 문서를 제거합니다.

Sequence Diagram

sequenceDiagram
    participant MainActivity as MainActivity
    participant BuyOrNotApp as BuyOrNotApp
    participant NavHost as BuyOrNotNavHost
    participant SplashRoute as SplashRoute
    participant ViewModel as SplashViewModel
    participant Repository as AppUpdateRepository
    participant RemoteConfig as RemoteConfigDataSource
    participant Preferences as AppPreferencesRepository
    participant Dialog as Update Dialog

    MainActivity->>BuyOrNotApp: onFinish: finishAffinity()
    BuyOrNotApp->>NavHost: onFinish callback
    NavHost->>SplashRoute: onFinish callback
    SplashRoute->>ViewModel: 스플래시 초기화
    ViewModel->>Repository: getAppUpdateInfo() 비동기 조회
    Repository->>RemoteConfig: fetchAppUpdateConfig()
    RemoteConfig-->>Repository: AppUpdateInfo 반환
    ViewModel->>Preferences: lastSoftUpdateShownTime 확인
    Repository-->>ViewModel: 버전 및 전략 정보 수신
    ViewModel->>ViewModel: UpdateStrategy 기반 DialogType 결정
    ViewModel-->>SplashRoute: uiState.updateDialogType 업데이트
    SplashRoute->>Dialog: SOFT/FORCE 다이얼로그 표시
    Dialog->>ViewModel: DismissSoftUpdate 인텐트
    ViewModel->>Preferences: lastSoftUpdateShownTime 저장
    Dialog->>SplashRoute: onFinish 콜백 (강제 업데이트)
    SplashRoute->>MainActivity: onFinish 실행
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Imagine-Choi
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/#83 업데이트 전략 구현' directly relates to and clearly summarizes the main change: implementing an update strategy feature controlled via Firebase Remote Config.
Linked Issues check ✅ Passed The PR implements all three objectives from issue #83: RemoteConfig 세팅 (RemoteConfigDataSourceImpl), 소프트 업데이트 적용 (SplashScreen soft update dialog, 24-hour suppression in AppPreferencesDataSource), and 강제 업데이트 적용 (SplashScreen forced update dialog with app exit via onFinish callback).
Out of Scope Changes check ✅ Passed All code changes are directly related to the update strategy feature. Documentation removals (CommitConvention.md, Modularization.md, SnackbarStateManagement.md) and code reformatting (@Inject constructor style unification) are acknowledged as part of the commit messages and represent cleanup/housekeeping activities aligned with PR objectives.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#83-update-popup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (1)
feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/navigation/AuthNavigation.kt (1)

27-33: KDoc에 onFinish 파라미터 문서화 누락

@param onFinish 설명이 KDoc에 추가되지 않았습니다. 강제 업데이트 시 앱 종료를 위한 콜백임을 명시하면 좋겠습니다.

📝 제안하는 수정
 * `@param` onNavigateToLogin 로그인 화면으로 이동할 때 실행될 콜백
 * `@param` onNavigateToHome 홈 화면으로 이동할 때 실행될 콜백
+ * `@param` onFinish 강제 업데이트 다이얼로그에서 앱 종료 시 실행될 콜백
 */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/navigation/AuthNavigation.kt`
around lines 27 - 33, KDoc for the NavGraphBuilder.splashScreen function is
missing documentation for the onFinish parameter; update the KDoc block for
splashScreen to add an `@param` onFinish entry that clearly states it is a
callback invoked to finish/exit the app (e.g., "앱 종료를 위한 콜백, 스플래시 완료 시 호출됨"), so
readers see the purpose of onFinish alongside onNavigateToLogin and
onNavigateToHome.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSourceImpl.kt`:
- Around line 35-36: RemoteConfigDataSourceImpl currently lets exceptions from
remoteConfig.fetchAndActivate().await() bubble up, which causes
SplashViewModel.checkTokenAndNavigate()/appUpdateRepository.getAppUpdateInfo()
to see updateInfo==null and force UpdateDialogType.None; wrap the
fetchAndActivate() call in a try/catch inside RemoteConfigDataSourceImpl (around
the fetchAndActivate().await() usage), log the exception, and return a safe
fallback (e.g., false or cached/previous Remote Config values) instead of
rethrowing so transient Remote Config failures are absorbed at this layer and
downstream callers like determineDialogType can continue using defaults or
cached values.

In `@docs/PR-bugfix-65-qa-2.md`:
- Around line 1-53: The PR includes a documentation file that documents a
different change set (it contains "closed `#65`" and the bugfix/refactor list)
while the PR title and scope are about "#83 업데이트 전략 구현"; either replace the
document content so it accurately describes the `#83` update strategy (update the
top header, checklist, Work Description and changed-file lists to reflect `#83`)
or remove/exclude this markdown from the commit/PR; locate the markdown that
currently contains "closed `#65`" and the Work Description sections (the
PR-bugfix-65-qa-2.md content) and update or remove it, then re-commit so the PR
body and changed files match the `#83` scope.
- Around line 37-40: Update the document to reflect the actual behavior of
HomeViewModel.optimisticVoteUpdate by replacing "API 응답 확정 전까지 UI에 결과를 반영하지 않도록
처리" with "수정: 투표 시 낙관적 업데이트로 투표 결과를 즉시 반영하도록 처리" and add a brief note
referencing HomeViewModel.optimisticVoteUpdate as the implemented method; if
instead you intend to prevent premature exposure, explicitly describe the
mitigation approach that would be implemented (e.g., delaying UI update until
API confirms) and reference the same HomeViewModel.optimisticVoteUpdate as the
place to change.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`:
- Around line 116-119: The dismissSoftUpdate function currently only clears
updateDialogType after the timestamp save completes, which can block
checkTokenAndNavigate if updateLastSoftUpdateShownTime fails or stalls; change
dismissSoftUpdate (and its viewModelScope.launch) to call updateState {
it.copy(updateDialogType = UpdateDialogType.None) } in a finally block so the
dialog state is always cleared regardless of success, and move
appPreferencesRepository.updateLastSoftUpdateShownTime(...) into the try block
(optionally catch and log the exception) to ensure checkTokenAndNavigate is not
indefinitely blocked.
- Around line 100-104: The FORCE branch currently returns UpdateDialogType.Force
whenever updateInfo.updateStrategy == UpdateStrategy.FORCE, which ignores
latestVersion; change the condition to require the user is actually behind the
latest release (i.e., combine updateInfo.updateStrategy == UpdateStrategy.FORCE
with currentVersion < updateInfo.latestVersion) so that UpdateDialogType.Force
is only returned for users with currentVersion < updateInfo.latestVersion (keep
the existing currentVersion < updateInfo.minimumVersion and the SOFT branch
logic intact); update the when expression that references currentVersion,
updateInfo.minimumVersion, updateInfo.latestVersion, and
updateInfo.updateStrategy accordingly.

---

Nitpick comments:
In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/navigation/AuthNavigation.kt`:
- Around line 27-33: KDoc for the NavGraphBuilder.splashScreen function is
missing documentation for the onFinish parameter; update the KDoc block for
splashScreen to add an `@param` onFinish entry that clearly states it is a
callback invoked to finish/exit the app (e.g., "앱 종료를 위한 콜백, 스플래시 완료 시 호출됨"), so
readers see the purpose of onFinish alongside onNavigateToLogin and
onNavigateToHome.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 244fb78f-b697-4f15-babb-dc02d4d81505

📥 Commits

Reviewing files that changed from the base of the PR and between d1607a7 and a72f0e3.

📒 Files selected for processing (22)
  • app/src/main/java/com/sseotdabwa/buyornot/MainActivity.kt
  • app/src/main/java/com/sseotdabwa/buyornot/navigation/BuyOrNotNavHost.kt
  • app/src/main/java/com/sseotdabwa/buyornot/ui/BuyOrNotApp.kt
  • app/src/main/java/com/sseotdabwa/buyornot/ui/BuyOrNotViewModel.kt
  • core/data/build.gradle.kts
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSource.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSourceImpl.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/di/DataModule.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/di/RemoteConfigModule.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppPreferencesRepositoryImpl.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppUpdateRepositoryImpl.kt
  • core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSource.kt
  • core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSourceImpl.kt
  • docs/PR-bugfix-65-qa-2.md
  • domain/src/main/java/com/sseotdabwa/buyornot/domain/model/AppUpdateInfo.kt
  • domain/src/main/java/com/sseotdabwa/buyornot/domain/repository/AppPreferencesRepository.kt
  • domain/src/main/java/com/sseotdabwa/buyornot/domain/repository/AppUpdateRepository.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/navigation/AuthNavigation.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashContract.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashScreen.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt
  • gradle/libs.versions.toml

Comment thread docs/PR-bugfix-65-qa-2.md Outdated
Comment thread docs/PR-bugfix-65-qa-2.md Outdated
Copy link
Copy Markdown

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

♻️ Duplicate comments (2)
feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt (2)

118-123: ⚠️ Potential issue | 🟠 Major

타임스탬프 저장 실패 시 스플래시에서 영구 대기 상태에 빠집니다.

updateLastSoftUpdateShownTime 호출이 실패하거나 지연되면 updateState가 실행되지 않아 checkTokenAndNavigate()uiState.first { it.updateDialogType == UpdateDialogType.None } 조건이 충족되지 않습니다. 사용자가 "나중에" 버튼을 눌러도 앱이 멈춘 상태로 남게 됩니다.

🛠️ finally 블록으로 상태 해제 보장
         private fun dismissSoftUpdate() {
             viewModelScope.launch {
-                appPreferencesRepository.updateLastSoftUpdateShownTime(System.currentTimeMillis())
-                updateState { it.copy(updateDialogType = UpdateDialogType.None) }
+                try {
+                    appPreferencesRepository.updateLastSoftUpdateShownTime(System.currentTimeMillis())
+                } catch (e: CancellationException) {
+                    throw e
+                } catch (e: Exception) {
+                    Log.w(TAG, "Failed to persist soft update dismissal", e)
+                } finally {
+                    updateState { it.copy(updateDialogType = UpdateDialogType.None) }
+                }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`
around lines 118 - 123, In dismissSoftUpdate ensure updateDialogType is reset
even if appPreferencesRepository.updateLastSoftUpdateShownTime fails by moving
updateState into a finally (or use runCatching { ... }.onFailure { ... } .also {
updateState(...) } pattern); specifically, in the dismissSoftUpdate function
call updateLastSoftUpdateShownTime inside a try and call updateState {
it.copy(updateDialogType = UpdateDialogType.None) } in finally so that
checkTokenAndNavigate's uiState.first { it.updateDialogType ==
UpdateDialogType.None } can proceed regardless of persistence errors.

102-104: ⚠️ Potential issue | 🔴 Critical

FORCE 전략에서 버전 비교가 누락되어 최신 버전 사용자도 차단됩니다.

현재 로직은 updateStrategy == FORCE일 때 버전 비교 없이 바로 UpdateDialogType.Force를 반환합니다. Remote Config를 FORCE로 설정하는 순간 이미 최신 버전을 사용 중인 사용자도 강제 업데이트 팝업에 막히게 됩니다.

🛠️ 버전 비교 조건 추가
             return when {
                 currentVersion < updateInfo.minimumVersion -> UpdateDialogType.Force
-                updateInfo.updateStrategy == UpdateStrategy.FORCE -> UpdateDialogType.Force
+                updateInfo.updateStrategy == UpdateStrategy.FORCE &&
+                    currentVersion < updateInfo.latestVersion -> UpdateDialogType.Force
                 updateInfo.updateStrategy == UpdateStrategy.SOFT &&
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`
around lines 102 - 104, The force-update branch in SplashViewModel erroneously
returns UpdateDialogType.Force whenever updateInfo.updateStrategy ==
UpdateStrategy.FORCE without checking the user's currentVersion; change the
condition so the FORCE strategy only triggers a forced update when the user is
actually below the required version (e.g., require currentVersion <
updateInfo.minimumVersion in the branch that checks updateInfo.updateStrategy ==
UpdateStrategy.FORCE), referencing the currentVersion,
updateInfo.minimumVersion, updateInfo.updateStrategy and UpdateDialogType to
locate and fix the logic.
🧹 Nitpick comments (2)
feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt (2)

72-75: 패키지 정보 조회 예외 처리 고려

getPackageInfo()는 이론적으로 NameNotFoundException을 던질 수 있습니다. 자체 패키지 조회이므로 실제로 발생할 가능성은 거의 없지만, 방어적 코딩 차원에서 예외 처리를 추가하면 더 안전합니다.

♻️ 방어적 예외 처리 추가
                 val currentVersion =
-                    PackageInfoCompat
-                        .getLongVersionCode(context.packageManager.getPackageInfo(context.packageName, 0))
-                        .toInt()
+                    runCatching {
+                        PackageInfoCompat
+                            .getLongVersionCode(context.packageManager.getPackageInfo(context.packageName, 0))
+                            .toInt()
+                    }.getOrElse {
+                        Log.e(TAG, "Failed to get package version", it)
+                        0 // 버전 0은 모든 minimumVersion보다 낮으므로 강제 업데이트 트리거
+                    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`
around lines 72 - 75, Wrap the package info lookup in a try/catch in
SplashViewModel around the call to
PackageInfoCompat.getLongVersionCode(context.packageManager.getPackageInfo(context.packageName,
0)) to handle android.content.pm.PackageManager.NameNotFoundException; on catch,
log the exception (or use existing logger) and use a safe default for
currentVersion (e.g., 0) so the app remains stable if package info cannot be
retrieved.

59-66: 예외 로깅 누락으로 디버깅이 어려워질 수 있습니다.

userPreferencesRepository.userType.first() 호출 시 발생하는 예외가 무시되고 있어, 프로덕션에서 토큰 검증 실패 원인을 추적하기 어렵습니다.

♻️ 예외 로깅 추가 제안
                 val hasValidToken =
                     try {
                         userPreferencesRepository.userType.first() != UserType.GUEST
                     } catch (e: CancellationException) {
                         throw e
                     } catch (e: Exception) {
+                        Log.w(TAG, "Failed to check user token", e)
                         false
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`
around lines 59 - 66, The exception thrown from
userPreferencesRepository.userType.first() inside the hasValidToken computation
is being swallowed, making post-deploy debugging hard; update the catch (e:
Exception) branch in SplashViewModel's hasValidToken logic to log the exception
(preserving the existing CancellationException rethrow) before returning
false—use the project's logging facility (e.g., Logger/Timber/Log) and include
context like "failed to read userType in SplashViewModel" and the exception so
the root cause is recorded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`:
- Around line 118-123: In dismissSoftUpdate ensure updateDialogType is reset
even if appPreferencesRepository.updateLastSoftUpdateShownTime fails by moving
updateState into a finally (or use runCatching { ... }.onFailure { ... } .also {
updateState(...) } pattern); specifically, in the dismissSoftUpdate function
call updateLastSoftUpdateShownTime inside a try and call updateState {
it.copy(updateDialogType = UpdateDialogType.None) } in finally so that
checkTokenAndNavigate's uiState.first { it.updateDialogType ==
UpdateDialogType.None } can proceed regardless of persistence errors.
- Around line 102-104: The force-update branch in SplashViewModel erroneously
returns UpdateDialogType.Force whenever updateInfo.updateStrategy ==
UpdateStrategy.FORCE without checking the user's currentVersion; change the
condition so the FORCE strategy only triggers a forced update when the user is
actually below the required version (e.g., require currentVersion <
updateInfo.minimumVersion in the branch that checks updateInfo.updateStrategy ==
UpdateStrategy.FORCE), referencing the currentVersion,
updateInfo.minimumVersion, updateInfo.updateStrategy and UpdateDialogType to
locate and fix the logic.

---

Nitpick comments:
In
`@feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt`:
- Around line 72-75: Wrap the package info lookup in a try/catch in
SplashViewModel around the call to
PackageInfoCompat.getLongVersionCode(context.packageManager.getPackageInfo(context.packageName,
0)) to handle android.content.pm.PackageManager.NameNotFoundException; on catch,
log the exception (or use existing logger) and use a safe default for
currentVersion (e.g., 0) so the app remains stable if package info cannot be
retrieved.
- Around line 59-66: The exception thrown from
userPreferencesRepository.userType.first() inside the hasValidToken computation
is being swallowed, making post-deploy debugging hard; update the catch (e:
Exception) branch in SplashViewModel's hasValidToken logic to log the exception
(preserving the existing CancellationException rethrow) before returning
false—use the project's logging facility (e.g., Logger/Timber/Log) and include
context like "failed to read userType in SplashViewModel" and the exception so
the root cause is recorded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: cfcc235f-f1c2-49a2-95ad-76602011bfef

📥 Commits

Reviewing files that changed from the base of the PR and between a72f0e3 and 155fecd.

📒 Files selected for processing (2)
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashContract.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashContract.kt

DongChyeon and others added 4 commits April 2, 2026 23:59
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/UserPreferencesDataSourceImpl.kt (1)

41-45: 예외 처리 시 로깅 추가 고려

UserType.valueOf() 파싱 실패 시 기본값으로 폴백하는 방어적 패턴은 적절하지만, 디버깅을 위해 로깅을 추가하면 더 좋습니다. (동일한 패턴이 lines 55-59에도 존재)

📝 로깅 추가 제안
                     prefs[Keys.USER_TYPE]?.let {
                         try {
                             UserType.valueOf(it)
                         } catch (e: IllegalArgumentException) {
+                            // Log.w("UserPreferences", "Invalid UserType value: $it", e)
                             UserPreferences().userType
                         }
                     } ?: UserPreferences().userType,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/UserPreferencesDataSourceImpl.kt`
around lines 41 - 45, The code in UserPreferencesDataSourceImpl is swallowing
IllegalArgumentException from UserType.valueOf(...) and silently falling back to
UserPreferences().userType; update the catch block to log the invalid input
value and the exception (include the offending string and e) using the module
logger before returning the default, and apply the same logging change to the
second identical parsing spot that also uses UserType.valueOf(...) so failures
are recorded for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/UserPreferencesDataSourceImpl.kt`:
- Around line 41-45: The code in UserPreferencesDataSourceImpl is swallowing
IllegalArgumentException from UserType.valueOf(...) and silently falling back to
UserPreferences().userType; update the catch block to log the invalid input
value and the exception (include the offending string and e) using the module
logger before returning the default, and apply the same logging change to the
second identical parsing spot that also uses UserType.valueOf(...) so failures
are recorded for debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 55d6af08-1400-490d-9c61-9689ee51cb68

📥 Commits

Reviewing files that changed from the base of the PR and between 155fecd and 12c0dc6.

📒 Files selected for processing (9)
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSourceImpl.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppUpdateRepositoryImpl.kt
  • core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSourceImpl.kt
  • core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/UserPreferencesDataSourceImpl.kt
  • core/network/src/main/java/com/sseotdabwa/buyornot/core/network/AuthEventBus.kt
  • docs/CommitConvention.md
  • docs/Modularization.md
  • docs/SnackbarStateManagement.md
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt
💤 Files with no reviewable changes (3)
  • docs/CommitConvention.md
  • docs/Modularization.md
  • docs/SnackbarStateManagement.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/AppUpdateRepositoryImpl.kt
  • core/datastore/src/main/java/com/sseotdabwa/buyornot/core/datastore/AppPreferencesDataSourceImpl.kt
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/datasource/RemoteConfigDataSourceImpl.kt
  • feature/auth/src/main/java/com/sseotdabwa/buyornot/feature/auth/ui/SplashViewModel.kt

@DongChyeon DongChyeon merged commit da14796 into develop Apr 2, 2026
2 checks passed
@DongChyeon DongChyeon deleted the feature/#83-update-popup branch April 2, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ FEAT 기능 개발 (애매하면 기능 개발로 두도록 하자) 💪 동현동현동현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feature - 업데이트 전략 구현

1 participant