Conversation
Walkthrough앱 업데이트 체크 및 다이얼로그 표시 로직을 MainActivity/MainViewModel에서 HomeScreen/HomeViewModel으로 이관합니다. 새로운 업데이트 사이드 이펙트 두 개를 Home으로 옮기고, Main에서는 관련 로직을 제거하며, Play Store 연동 기능을 HomeScreen에 구현합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant HomeScreen
participant HomeViewModel
participant GetAppVersionUseCase
participant DialogController
participant PlayStore
HomeViewModel->>GetAppVersionUseCase: init → checkAppVersion()
GetAppVersionUseCase->>GetAppVersionUseCase: 설치된 버전 vs 최신 버전 비교
alt 필수 업데이트 필요
GetAppVersionUseCase->>HomeViewModel: ShowUpdateRequiredDialog
else 선택 업데이트 필요
GetAppVersionUseCase->>HomeViewModel: ShowUpdateOptionalDialog
end
HomeViewModel->>HomeScreen: sideEffect 발행
HomeScreen->>DialogController: 다이얼로그 표시
DialogController-->>HomeScreen: 사용자 입력 수신
HomeScreen->>HomeScreen: openPlayStoreForUpdate(context)
HomeScreen->>PlayStore: Intent로 Play Store 앱 실행
alt Play Store 앱 미설치
HomeScreen->>PlayStore: 웹 브라우저로 URL 오픈 (fallback)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 세심한 검토가 필요한 영역:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeContract.kt(1 hunks)app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeScreen.kt(11 hunks)app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt(9 hunks)app/src/main/java/com/depromeet/team6/presentation/ui/main/MainActivity.kt(1 hunks)app/src/main/java/com/depromeet/team6/presentation/ui/main/MainContract.kt(1 hunks)app/src/main/java/com/depromeet/team6/presentation/ui/main/MainViewModel.kt(0 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/com/depromeet/team6/presentation/ui/main/MainViewModel.kt
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-18T12:10:51.782Z
Learnt from: CR
Repo: depromeet/16th-team6-Android PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-10-18T12:10:51.782Z
Learning: Adopt Clean Architecture with MVI and unidirectional data flow (Intent -> ViewModel -> State -> UI)
Applied to files:
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt
📚 Learning: 2025-10-18T12:10:51.782Z
Learnt from: CR
Repo: depromeet/16th-team6-Android PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-10-18T12:10:51.782Z
Learning: Separate layers: UI with Jetpack Compose subscribing to ViewModel state; Domain as pure Kotlin with UseCases; Data using repositories abstracting sources
Applied to files:
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.ktapp/src/main/java/com/depromeet/team6/presentation/ui/main/MainActivity.ktapp/src/main/java/com/depromeet/team6/presentation/ui/home/HomeScreen.kt
📚 Learning: 2025-10-18T12:10:51.782Z
Learnt from: CR
Repo: depromeet/16th-team6-Android PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-10-18T12:10:51.782Z
Learning: Use AAC ViewModel and Navigation for lifecycle-aware state and app navigation
Applied to files:
app/src/main/java/com/depromeet/team6/presentation/ui/main/MainActivity.ktapp/src/main/java/com/depromeet/team6/presentation/ui/home/HomeScreen.kt
🧬 Code graph analysis (1)
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt (1)
app/src/main/java/com/depromeet/team6/presentation/util/base/BaseViewModel.kt (3)
setState(39-41)setEvent(43-45)setSideEffect(103-105)
🪛 detekt (1.23.8)
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt
[warning] 769-769: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
⏰ 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
🔇 Additional comments (5)
app/src/main/java/com/depromeet/team6/presentation/ui/main/MainContract.kt (1)
18-18: 업데이트 관련 사이드 이펙트가 Home으로 올바르게 이동되었습니다.
MainSideEffect를 비어있는 sealed interface로 유지하는 것은 깔끔한 리팩토링입니다. 업데이트 다이얼로그 책임이 이제HomeSideEffect에 있으므로 Main 레이어의 관심사가 명확해졌습니다.app/src/main/java/com/depromeet/team6/presentation/ui/main/MainActivity.kt (1)
131-160: 리뷰 요청사항이 이미 정상 구현되었습니다. 마이그레이션 완료 확인됨.검증 결과, MainActivity에서 제거된 업데이트 다이얼로그 로직이 Home 모듈에 완전히 구현되어 있음을 확인했습니다:
- HomeContract.kt:
ShowUpdateRequiredDialog,ShowUpdateOptionalDialog사이드 이펙트 정의됨- HomeScreen.kt: 두 다이얼로그 모두 적절히 처리 중 (
openPlayStoreForUpdate()함수 포함)- HomeViewModel.kt:
GetAppVersionUseCase임포트되어 버전 체크 기능 보유아키텍처 개선(Main 시작 시점→ Home 진입 시점)이 정상 완료되었으므로 코드는 안정적인 상태입니다.
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeContract.kt (1)
86-87: LGTM!업데이트 다이얼로그를 위한 사이드 이펙트 추가가 MVI 패턴에 맞게 잘 구현되었습니다.
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeScreen.kt (1)
1052-1073: 상수 정의 확인 완료 - 구현 정상검증 결과,
AppConstants.PLAY_STORE_PACKAGE_NAME과AppConstants.PLAY_STORE_URL모두 올바르게 정의되어 있습니다:
PLAY_STORE_PACKAGE_NAME = "com.depromeet.team6"(Constraints.kt:28)PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=$PLAY_STORE_PACKAGE_NAME&hl=ko"(Constraints.kt:29-30)함수 구현도 적절합니다. Play Store 앱 우선 시도 후 웹 폴백이 정상 동작하며, 에러 처리와 플래그 설정도 올바릅니다.
app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt (1)
74-74: 버전 체크 호출 시점은 적절함 - 추가 검증 불필요분석 결과, HomeViewModel의 init 블록에서 checkAppVersion() 호출하는 것은 안전합니다:
- 엔드포인트 특성:
/app/version엔드포인트는 public 엔드포인트로 인증이 필요하지 않으므로, 빈 헤더로 요청해도 API가 응답합니다.- 토큰 가용성: HomeViewModel은 로그인 후 메인 화면으로 표시되므로, 이 시점에서 토큰은 이미 초기화되어 있습니다.
- 비동기 실행:
viewModelScope.launch를 통한 비동기 실행은 defensive programming으로서 적절합니다.PR의 변경사항(MainActivity init → HomeViewModel init)은 앱 시작 시 불필요한 작업을 지연시키는 좋은 아키텍처 개선입니다.
| is HomeContract.HomeSideEffect.ShowUpdateRequiredDialog -> { | ||
| dialogController.showAtchaOneButtonAlert( | ||
| message = "더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요", | ||
| onConfirm = { openPlayStoreForUpdate(context = context) }, | ||
| confirmButtonText = "업데이트 하기" | ||
| ) | ||
| } | ||
|
|
||
| is HomeContract.HomeSideEffect.ShowUpdateOptionalDialog -> { | ||
| dialogController.showAtchaTwoButtonAlert( | ||
| message = "더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요", | ||
| onConfirm = { openPlayStoreForUpdate(context = context) }, | ||
| confirmButtonText = "업데이트 하기", | ||
| closeButtonText = "닫기" | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
다이얼로그 메시지를 string resources로 관리
Lines 194, 202에서 다이얼로그 메시지가 하드코딩되어 있습니다. 다국어 지원 및 유지보수를 위해 strings.xml로 이동하는 것을 권장합니다.
strings.xml에 추가:
<string name="update_dialog_message">더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요</string>
<string name="update_dialog_confirm">업데이트 하기</string>
<string name="update_dialog_close">닫기</string>코드 수정:
is HomeContract.HomeSideEffect.ShowUpdateRequiredDialog -> {
dialogController.showAtchaOneButtonAlert(
- message = "더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요",
+ message = stringResource(R.string.update_dialog_message),
onConfirm = { openPlayStoreForUpdate(context = context) },
- confirmButtonText = "업데이트 하기"
+ confirmButtonText = stringResource(R.string.update_dialog_confirm)
)
}🤖 Prompt for AI Agents
In app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeScreen.kt
around lines 192 to 207, the dialog strings are hardcoded; move them to string
resources and replace the literal text with resource lookups. Add the three
strings to res/values/strings.xml as suggested (update_dialog_message,
update_dialog_confirm, update_dialog_close), then update the calls to
dialogController.showAtchaOneButtonAlert and showAtchaTwoButtonAlert to use
context.getString(R.string.update_dialog_message),
context.getString(R.string.update_dialog_confirm), and
context.getString(R.string.update_dialog_close) respectively (ensure the Android
R import is available).
| private fun checkAppVersion() { | ||
| val installedVersion = getCurrentVersionName()?.cleanVersion() ?: "0.0.0" | ||
| viewModelScope.launch { | ||
| getAppVersionUseCase().onSuccess { appVersion -> | ||
| val latestVersion = appVersion.removePrefix("Success(") | ||
| .removePrefix("v") | ||
| .removeSuffix(")") | ||
| .cleanVersion() | ||
|
|
||
| Timber.d("latestVersion: $latestVersion, installedVersion: $installedVersion") | ||
|
|
||
| when (compareVersions(installedVersion, latestVersion)) { | ||
| VersionResult.UPDATE_REQUIRED -> { | ||
| setSideEffect(HomeContract.HomeSideEffect.ShowUpdateRequiredDialog) | ||
| } | ||
|
|
||
| VersionResult.UPDATE_OPTIONAL -> { | ||
| setSideEffect(HomeContract.HomeSideEffect.ShowUpdateOptionalDialog) | ||
| } | ||
|
|
||
| VersionResult.UP_TO_DATE -> { | ||
| Timber.d("최신 버전입니다") | ||
| } | ||
| } | ||
| }.onFailure { | ||
| Timber.e(it, "앱 버전 확인 실패") | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
응답 문자열 파싱이 취약함
Lines 715-718에서 응답을 removePrefix("Success(") 등으로 파싱하는 방식은 매우 취약합니다. UseCase가 이미 Result<String>을 반환한다면, 성공 시 문자열 내용에 "Success("가 포함될 이유가 없습니다.
GetAppVersionUseCase의 반환 타입을 확인하고, 정확한 응답 형식에 맞게 파싱 로직을 수정하세요. DTO를 사용하거나 최소한 응답 형식에 대한 가정을 검증하는 로직이 필요합니다.
🏁 Script executed:
#!/bin/bash
# GetAppVersionUseCase의 반환 타입과 구현 확인
ast-grep --pattern $'class GetAppVersionUseCase {
$$$
operator fun invoke($$$): $RETURN_TYPE {
$$$
}
$$$
}'
# 실제 API 응답 형식 확인
rg -n "appVersion|getAppVersion" --type=kotlin -C5Length of output: 10513
---
🏁 Script executed:
```shell
#!/bin/bash
# Find GetAppVersionUseCase implementation
find . -name "*GetAppVersionUseCase*" -type f
# Search for class definition
rg -n "class GetAppVersionUseCase|fun GetAppVersionUseCase" --type=kotlin -A20
Length of output: 2554
---
🏁 Script executed:
```shell
#!/bin/bash
# Find VersionRepository interface and implementations
rg -n "interface VersionRepository|class.*VersionRepository" --type=kotlin -A15
# Look for getVersion implementation
rg -n "fun getVersion|getVersion\(\)" --type=kotlin -B3 -A10 | head -80
Length of output: 5497
VersionRepositoryImpl에서 Result 타입을 문자열로 변환하는 심각한 버그
근본 원인은 HomeViewModel이 아니라 VersionRepositoryImpl.kt 라인 12에 있습니다.
// 현재 (잘못됨)
versionRemoteDataSource.getVersion().toString()versionRemoteDataSource.getVersion()은 Result<String>을 반환하는데, 여기서 .toString()을 호출하면 "Success(v1.0.0)" 같은 문자열 표현이 되어버립니다. 그 결과 HomeViewModel의 onSuccess 블록에서 받는 appVersion은 이미 "Success(v1.0.0)" 형태의 문자열이 되어있고, 이를 파싱하기 위해 removePrefix("Success(") 같은 우회책을 쓰고 있는 것입니다.
해결책: VersionRepositoryImpl.kt 라인 11-13을 다음과 같이 수정하세요:
override suspend fun getVersion(): Result<String> =
versionRemoteDataSource.getVersion()Result 타입 시맨틱을 제대로 존중하고, HomeViewModel의 파싱 로직도 제거할 수 있습니다.
| private fun getCurrentVersionName(): String? { | ||
| return try { | ||
| val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) | ||
| packageInfo.versionName | ||
| } catch (e: Exception) { | ||
| Timber.e(e, "Failed to get app version name") | ||
| "0.0.0" | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
구체적인 예외 타입 사용 필요
PackageManager.getPackageInfo()는 PackageManager.NameNotFoundException을 던지므로, 일반적인 Exception 대신 구체적인 예외 타입을 catch하는 것이 좋습니다.
As per static analysis hints.
다음과 같이 수정하세요:
private fun getCurrentVersionName(): String? {
return try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
packageInfo.versionName
- } catch (e: Exception) {
+ } catch (e: PackageManager.NameNotFoundException) {
Timber.e(e, "Failed to get app version name")
"0.0.0"
}
}📝 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.
| private fun getCurrentVersionName(): String? { | |
| return try { | |
| val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) | |
| packageInfo.versionName | |
| } catch (e: Exception) { | |
| Timber.e(e, "Failed to get app version name") | |
| "0.0.0" | |
| } | |
| } | |
| private fun getCurrentVersionName(): String? { | |
| return try { | |
| val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) | |
| packageInfo.versionName | |
| } catch (e: PackageManager.NameNotFoundException) { | |
| Timber.e(e, "Failed to get app version name") | |
| "0.0.0" | |
| } | |
| } |
🧰 Tools
🪛 detekt (1.23.8)
[warning] 769-769: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
🤖 Prompt for AI Agents
In app/src/main/java/com/depromeet/team6/presentation/ui/home/HomeViewModel.kt
around lines 765 to 773, the catch currently swallows all Exceptions; change it
to catch the specific PackageManager.NameNotFoundException instead of Exception,
keep the Timber.e logging and the "0.0.0" fallback return, and add the necessary
import for android.content.pm.PackageManager.NameNotFoundException if missing.
#️⃣연관된 이슈
📝작업 내용
앱 삭제 후 재설치시 토큰을 받아오기 전에 실행되어 빈 헤더로 요청하는 문제가 있었습니다.
PR 발행 전 체크 리스트
안해!
스크린샷 (선택)
없어!
💬리뷰 요구사항(선택)
Summary by CodeRabbit
새 기능