Skip to content

Conversation

wjdrjs00
Copy link
Member

@wjdrjs00 wjdrjs00 commented Aug 21, 2025

[ PR Content ]

앱 강제 업데이트 로직을 구현했습니다.

Related issue

Screenshot 📸

Screen_recording_20250821_163300.mp4

Work Description

  • 강제 업데이트 로직 구현
  • 앱 버전 api 연동
  • 버전 컨벤션 플러그인 설정

To Reviewers 📢

  • 스플래시 화면 진입 시 버전 확인 api의 응답(Boolean)여부를 통해 강제 업데이트를 유도하도록 구현했습니다.
  • 응답이 false 인 경우 자동 로그인 api를 호출하도록 구현했습니다! (강제 업데이트가 true응답이 올 경우 불필요한 자동로그인 api를 호출한다 판단하여 병렬처리는 하지 않았습니다.)
  • 추가로 생각해볼점:
    해당 api의 응답이 true로 오는 경우가 흔하게 발생하는 상황이 아니라 생각이 되어 추후 캐싱등을 활용해서 해당 api 호출을 줄이는 방법으로 리펙하는것도 괜찮을거 같습니다.

Summary by CodeRabbit

  • 신기능
    • 스플래시에서 앱 강제 업데이트 필요 여부를 확인합니다.
    • 강제 업데이트 필요 시 해제 불가 안내 다이얼로그를 표시하고, ‘업데이트’로 플레이 스토어를 열거나 ‘나가기’로 앱을 종료할 수 있습니다.
    • 네트워크 오류/시간초과(5초) 시 업데이트가 필요 없는 것으로 간주하고 기존 흐름을 계속합니다.
  • 작업
    • 버전 관리 방식을 major/minor/patch로 구조화하고 빌드에 버전 상수를 제공합니다. 사용자에게 보이는 변화는 없습니다.

@wjdrjs00 wjdrjs00 requested a review from l5x5l August 21, 2025 07:34
@wjdrjs00 wjdrjs00 self-assigned this Aug 21, 2025
@wjdrjs00 wjdrjs00 added ✨ Feature 새로운 기능 구현 🧤 대현 labels Aug 21, 2025
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

강제 업데이트 기능을 도메인-데이터-프레젠테이션 전 계층에 추가했습니다. 버전 API 연동(서비스/데이터소스/레포지토리/유즈케이스), DI 바인딩 추가, 스플래시 화면에서 강제 업데이트 체크/다이얼로그 표시/마켓 이동 처리, Gradle 버전 관리(major/minor/patch) 확장 및 적용을 포함합니다.

Changes

Cohort / File(s) Summary
Version API: Data/Domain
data/.../version/service/VersionService.kt, data/.../version/datasource/VersionDataSource.kt, data/.../version/datasourceImpl/VersionDataSourceImpl.kt, data/.../version/model/response/VersionCheckResponseDto.kt, data/.../version/repositoryImpl/VersionRepositoryImpl.kt, domain/.../version/repository/VersionRepository.kt, domain/.../version/model/UpdateRequirement.kt, domain/.../version/usecase/CheckUpdateRequirementUseCase.kt
신규 버전 체크 API 정의, DTO/매퍼 추가, 데이터소스/레포지토리 구현, 도메인 모델 및 유즈케이스 추가.
DI 구성 추가
app/src/main/java/.../di/data/ServiceModule.kt, .../di/data/DataSourceModule.kt, .../di/data/RepositoryModule.kt
VersionService/VersionDataSource/VersionRepository 바인딩 및 프로바이더 추가.
Presentation: Splash 강제 업데이트
presentation/.../splash/SplashViewModel.kt, presentation/.../splash/SplashScreen.kt, presentation/.../splash/model/SplashIntent.kt, presentation/.../splash/model/SplashState.kt, presentation/.../splash/component/template/ForceUpdateDialog.kt, presentation/.../splash/util/PlayStoreUtils.kt
강제 업데이트 상태/인텐트 추가, 초기 체크/게이팅 로직 도입, 다이얼로그 UI 추가, 플레이스토어 이동/앱 종료 유틸 추가.
Build logic: 버전 관리
build-logic/convention/.../AndroidApplicationPlugin.kt, .../AndroidLibraryPlugin.kt, .../extension/AppVersion.kt, gradle/libs.versions.toml
major/minor/patch 기반 버전 구성 확장 추가 및 적용, versionName 분해, BuildConfig 필드 주입.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SplashScreen
  participant SplashViewModel
  participant VersionUseCase as CheckUpdateRequirementUseCase
  participant Repo as VersionRepository
  participant DS as VersionDataSource
  participant Svc as VersionService
  participant BE as Backend

  User->>SplashScreen: 앱 실행
  SplashScreen->>SplashViewModel: 구독 시작
  SplashViewModel->>VersionUseCase: invoke()
  VersionUseCase->>Repo: checkVersion()
  Repo->>DS: checkVersion()
  DS->>Svc: GET /api/v1/version/android/check?major&minor&patch
  Svc->>BE: 요청
  BE-->>Svc: BaseResponse{ forceUpdateYn }
  Svc-->>DS: 응답
  DS-->>Repo: Result<VersionCheckResponseDto>
  Repo-->>VersionUseCase: Result<UpdateRequirement>
  VersionUseCase-->>SplashViewModel: Result<Boolean>(isForced)
  alt 강제 업데이트 필요
    SplashViewModel->>SplashScreen: uiState.forceUpdateRequired = true
    SplashScreen->>SplashScreen: ForceUpdateDialog 표시
    User->>SplashScreen: 업데이트 선택
    SplashScreen->>PlayStore: 앱 상세 화면 열기
    PlayStore-->>SplashScreen: 포그라운드 전환
    SplashScreen->>System: 앱 종료(옵션)
  else 불필요
    SplashViewModel->>SplashViewModel: 자동 로그인 진행
    SplashViewModel->>SplashScreen: 네비게이션 진행
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
강제 업데이트 UI 구현 (#116)
버전 API 연동 (#116)

Poem

새벽 부팅, 스플래시에 귀를 쫑긋 세우네 🐇
버전 바람 타고 온 소식, “업데이트가 필요해!”
딸깍, 상점 문을 톡톡 두드리고—
안녕, 구(舊)세상은 여기까지!
메이저·마이너·패치 셋이 나란히,
오늘도 안정 주행, 귀는 더 말랑해졌다.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#116-app-force-update

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (31)
gradle/libs.versions.toml (2)

8-11: Play 콘솔 업로드를 위해 versionCode 증가 보장 필요 — semver 기반 자동 계산으로 인적 오류 방지 제안

현재 patch(=버전명)만 올리고 versionCode(=8라인, "4")를 유지하면 스토어 업로드가 실패합니다. semver(major/minor/patch)로부터 versionCode를 계산하도록 플러그인 측에서 일원화하는 것을 권장합니다.

제안 A) AppVersion 확장에서 versionCode를 계산·설정하고, ApplicationPlugin 쪽의 수동 설정을 제거

*** build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt
@@
     val major = libs.findVersion("versionMajor").get().requiredVersion
     val minor = libs.findVersion("versionMinor").get().requiredVersion
     val patch = libs.findVersion("versionPatch").get().requiredVersion

     extensions.findByType<ApplicationExtension>()?.let { appExtension ->
         appExtension.defaultConfig {
             versionName = "$major.$minor.$patch"
+            // versionCode = major*10000 + minor*100 + patch
+            val majorInt = major.toInt()
+            val minorInt = minor.toInt()
+            val patchInt = patch.toInt()
+            versionCode = majorInt * 10000 + minorInt * 100 + patchInt
             buildConfigField("int", "VERSION_MAJOR", major)
             buildConfigField("int", "VERSION_MINOR", minor)
             buildConfigField("int", "VERSION_PATCH", patch)
         }
     }
*** build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidApplicationPlugin.kt
@@
             with(defaultConfig) {
                 targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
-                versionCode = libs.findVersion("versionCode").get().requiredVersion.toInt()
             }

제안 B) 현 구조 유지 시, 릴리스마다 libs.versions.toml의 versionCode를 반드시 증가시키는 릴리스 규칙을 문서화/자동화(CI 체크)하세요.

업로드 이력과의 정합성(기존 versionCode보다 커야 함)도 한 번 확인 부탁드립니다.


9-11: 선행 0(leading zero) 사용 금지 권장

minor/patch에 "01"처럼 선행 0이 들어가면 생성되는 BuildConfig int 리터럴이 혼란을 줄 수 있습니다. 현재 값은 문제없지만, 향후에도 "0", "2"처럼 선행 0 없는 10진 표기를 유지해 주세요.

build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt (3)

18-22: BuildConfig.VERSION_NAME(String)도 함께 노출하면 사용성↑

UI/로그/진단에서 버전 문자열을 그대로 쓰는 경우가 많습니다. 아래처럼 String 상수도 추가해 두면 호출부 편의가 큽니다.

 appExtension.defaultConfig {
     versionName = "$major.$minor.$patch"
+    buildConfigField("String", "VERSION_NAME", "\"$major.$minor.$patch\"")
     buildConfigField("int", "VERSION_MAJOR", major)
     buildConfigField("int", "VERSION_MINOR", minor)
     buildConfigField("int", "VERSION_PATCH", patch)
 }

라이브러리에도 동일 상수를 두고 싶다면 27~31 라인에도 동일하게 추가하세요.


12-14: Optional.get() 직접 호출 지양 — catalog 키 누락 시 더 친절한 에러 메시지 제공

키가 누락되면 .get()는 모호한 예외를 던집니다. orElseThrow로 명시적인 메시지를 제시하세요.

-    val major = libs.findVersion("versionMajor").get().requiredVersion
-    val minor = libs.findVersion("versionMinor").get().requiredVersion
-    val patch = libs.findVersion("versionPatch").get().requiredVersion
+    val major = libs.findVersion("versionMajor")
+        .orElseThrow { IllegalStateException("libs.versions.toml: versions.versionMajor가 누락되었습니다.") }
+        .requiredVersion
+    val minor = libs.findVersion("versionMinor")
+        .orElseThrow { IllegalStateException("libs.versions.toml: versions.versionMinor가 누락되었습니다.") }
+        .requiredVersion
+    val patch = libs.findVersion("versionPatch")
+        .orElseThrow { IllegalStateException("libs.versions.toml: versions.versionPatch가 누락되었습니다.") }
+        .requiredVersion

25-35: library의 buildConfig 활성화 순서를 defaultConfig보다 먼저 배치 권장

구성 시점엔 큰 문제는 없겠지만, 의도를 더 명확히 하기 위해 buildFeatures를 먼저 두는 편이 읽기 좋습니다.

-    extensions.findByType<LibraryExtension>()?.let { libExtension ->
-        libExtension.apply {
-            defaultConfig {
-                buildConfigField("int", "VERSION_MAJOR", major)
-                buildConfigField("int", "VERSION_MINOR", minor)
-                buildConfigField("int", "VERSION_PATCH", patch)
-            }
-            buildFeatures {
-                buildConfig = true
-            }
-        }
-    }
+    extensions.findByType<LibraryExtension>()?.let { libExtension ->
+        libExtension.apply {
+            buildFeatures { buildConfig = true }
+            defaultConfig {
+                buildConfigField("int", "VERSION_MAJOR", major)
+                buildConfigField("int", "VERSION_MINOR", minor)
+                buildConfigField("int", "VERSION_PATCH", patch)
+            }
+        }
+    }
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidApplicationPlugin.kt (1)

26-29: versionCode 설정의 단일화 필요 — AppVersion 확장으로 이동시 여기서는 제거

AppVersion.kt에서 versionCode를 계산·설정하도록 바꾸는 경우, 중복 설정을 방지하기 위해 아래 라인을 제거하세요.

 with(defaultConfig) {
     targetSdk = libs.findVersion("targetSdk").get().requiredVersion.toInt()
-    versionCode = libs.findVersion("versionCode").get().requiredVersion.toInt()
 }
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidLibraryPlugin.kt (1)

3-5: LibraryExtension 타입 통일 권장(api.dsl 사용)

현재 파일은 com.android.build.gradle.LibraryExtension(Line 3), AppVersion.kt는 com.android.build.api.dsl.LibraryExtension을 사용합니다. AGP 8+ 기준으론 api.dsl 타입 사용이 권장되며, AppVersion.kt의 findByType<LibraryExtension>()과의 타입 정합성도 보장됩니다. 아래처럼 import를 교체해 주세요.

-import com.android.build.gradle.LibraryExtension
+import com.android.build.api.dsl.LibraryExtension

추가 변경 없이 extensions.configure<LibraryExtension> { ... }는 그대로 동작합니다.

domain/src/main/java/com/threegap/bitnagil/domain/version/model/UpdateRequirement.kt (2)

3-5: 불리언 하나로는 확장성에 제약이 큽니다: 정책 모델로 확장 고려 요청

현재 isForced: Boolean만으로는 향후 “선택 업데이트”, “최소 지원 버전”, “사유/메시지” 등 요구사항 추가 시 도메인 모델 변경이 불가피합니다. sealed class(예: UpdatePolicy.None/Optional/Forced(minVersion, reason?))나 enum class+부가 필드로의 확장을 검토해 주세요. 프레젠테이션/도메인 계층 간 의미 손실도 줄일 수 있습니다.


3-5: 도메인 객체를 끝까지 전달하는 일관성 제안

AI 요약에 따르면 UseCase 단계에서 다시 Result<Boolean>로 축소됩니다. 도메인 정보를 보존하려면 UseCase 반환을 Result<UpdateRequirement>로 유지하고, UI 계층에서 필요한 필드만 꺼내 쓰는 편이 이후 요구사항(메시지/버전 등) 대응에 유리합니다.

이 변경이 가능하면 연쇄적으로 참조되는 타입들(레포지토리/뷰모델) 시그니처가 모두 컴파일되는지 확인해 주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/splash/component/template/ForceUpdateDialog.kt (3)

49-49: 하드코딩된 문자열을 string 리소스로 이동하세요

UI 텍스트는 현지화/A11y/일관성 측면에서 리소스로 관리하는 것이 기본입니다. stringResource(R.string.xxx)로 교체해 주세요.

예시:

- text = "최신 버전으로 업데이트가 필요해요",
+ text = stringResource(R.string.force_update_title),
- text = "더 안정적이고 안전한 서비스를 위해\n최신 버전으로 업데이트해 주세요.",
+ text = stringResource(R.string.force_update_body),

필요 시 import:

import androidx.compose.ui.res.stringResource

그리고 presentation 모듈의 res/values/strings.xml에 키를 추가해 주세요.

Also applies to: 60-60


27-31: 파라미터명 의미 혼동: onDismissRequest는 시스템 해제 콜백, “나가기”는 onExitClick으로 분리 제안

현재 “나가기” 버튼이 onDismissRequest를 직접 호출합니다. onDismissRequest는 시스템에 의한 dismiss 의도에 대응하는 콜백인지라 의미가 섞입니다. API 명세를 명확히 하려면 버튼 클릭용 onExitClick을 분리하세요.

제안 diff:

- fun ForceUpdateDialog(
-     onUpdateClick: () -> Unit,
-     onDismissRequest: () -> Unit,
-     modifier: Modifier = Modifier,
- ) {
-     BasicAlertDialog(
-         onDismissRequest = onDismissRequest,
+ fun ForceUpdateDialog(
+     onUpdateClick: () -> Unit,
+     onExitClick: () -> Unit,
+     modifier: Modifier = Modifier,
+ ) {
+     BasicAlertDialog(
+         onDismissRequest = {},
          ...
      ) {
         ...
-        BitnagilTextButton(
-            text = "나가기",
-            onClick = onDismissRequest,
+        BitnagilTextButton(
+            text = "나가기",
+            onClick = onExitClick,
             ...
         )

호출부 변경도 함께 반영해 주세요.

Also applies to: 33-33, 77-79


95-102: Preview에서 테마 미적용 가능성: BitnagilTheme로 감싸기 권장

디자인 토큰이 CompositionLocal에 의존한다면 프리뷰가 깨질 수 있습니다. 테마로 감싸 주세요.

예시:

@Preview
@Composable
private fun ForceUpdateDialogPreview() {
    BitnagilTheme {
        ForceUpdateDialog(
            onUpdateClick = {},
            onExitClick = {},
        )
    }
}
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/util/PlayStoreUtils.kt (1)

64-70: 프로세스 강종은 가급적 피하세요

exitProcess(0)는 UX와 안정성 측면에서 권장되지 않습니다. finishAffinity()만 호출하거나, 앱을 백그라운드로 보내는 방식을 검토해 주세요. 강제 업데이트 정책상 종료가 불가피하다면, 최소한 토스트/스낵바로 안내 후 종료하거나 종료 지연 시간을 조금 늘려 스토어 전환이 안정적으로 완료되도록 하는 방식을 권장합니다.

실기기에서 Play 스토어 전환 직후 앱이 종료되어 복귀 버튼 동작에 문제가 없는지 QA로 확인 부탁드립니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashIntent.kt (1)

12-12: Intent 네이밍/페이로드 정교화 제안

SetForceUpdateResult(Boolean)은 상태 설정 느낌이라 이벤트 의미가 약합니다. ForceUpdateCheckCompleted(isRequired: Boolean)처럼 “이벤트”를 드러내거나, 도메인 모델(UpdateRequirement) 자체를 전달해 의미 손실을 줄이는 것도 고려해 주세요. 기존 SetUserRole 컨벤션을 유지할지, “이벤트 중심”으로 정리할지는 팀 합의에 따릅니다.

해당 Intent를 처리하는 리듀서에서 네이밍/의도에 맞는 사이드이펙트(Navigation, Dialog 노출)가 정확히 분리되어 있는지 한번 더 점검해 주세요.

app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt (1)

3-9: 패키지 네이밍 컨벤션(repositoryImpl vs repositoryimpl) 혼용

  • Line 3: repositoryimpl.AuthRepositoryImpl
  • Line 9: repositoryImpl.VersionRepositoryImpl

같은 모듈 내에서 패키지 케이스가 혼용되어 있어 탐색성/유지보수성이 떨어집니다. 가능하면 패키지명 컨벤션을 통일하는 정리가 필요합니다. (예: 전부 repositoryImpl로 통일)

app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt (1)

25-63: 메서드 네이밍 일관성(선택 사항)

동일 파일 내에 provideXxxServiceproviderXxxService가 혼재합니다. 새로 추가된 provideVersionService는 ‘provide’ 스타일과 일치합니다. 나머지 providerOnBoardingService, providerEmotionService, providerWriteRoutineService를 추후 일괄 정리하면 가독성이 좋아집니다. (범위가 넓어 본 PR에선 선택 사항)

data/src/main/java/com/threegap/bitnagil/data/version/datasource/VersionDataSource.kt (1)

5-7: 단순·명확한 데이터소스 계약 LGTM. 다만 Kotlin Result 노출에 대한 팀 합의 권장

  • 인터페이스가 깔끔합니다. 데이터 레이어에서 DTO를 반환하고 상위에서 매핑하는 흐름도 분명합니다.
  • Kotlin Result<T>를 퍼블릭 API로 노출할 경우, 팀 내 일관된 에러 매핑/로깅 정책 합의가 있으면 좋습니다. 대안으로 sealed class NetworkResult 등을 쓰면 에러 타입 표현력이 올라갑니다. (선택)

다음과 같이 KDoc을 추가해 반환 규약과 실패 케이스를 명시하면 호출부 이해도가 올라갑니다.

 interface VersionDataSource {
-    suspend fun checkVersion(): Result<VersionCheckResponseDto>
+    /**
+     * 서버에 현재 앱 버전(major/minor/patch)을 전달하여 강제 업데이트 필요 여부를 조회합니다.
+     *
+     * @return Result.success(VersionCheckResponseDto) 성공 시 DTO, Result.failure(Throwable) 통신/파싱 등 실패 사유
+     */
+    suspend fun checkVersion(): Result<VersionCheckResponseDto>
 }
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashState.kt (1)

11-13: isForceUpdateCheckCompleted 필드 활용 여부 검토 및 정리 제안

스크립트 확인 결과:

  • forceUpdateRequired
    • SplashViewModel 및 SplashScreen에서 정상적으로 조건문에 활용되고 있습니다 (SplashViewModel.kt:47–48, SplashViewModel.kt:103, SplashScreen.kt:60).
  • isForceUpdateCheckCompleted
    • SetForceUpdateResult 처리 시에만 true로 설정될 뿐, 이후 네비게이션/자동로그인 로직의 게이트로 사용되는 부분이 없습니다.
  • SplashState.kt (Lines 11–12)
        val isForceUpdateCheckCompleted: Boolean = false,
        val forceUpdateRequired: Boolean = false,

제안 사항:

  • isForceUpdateCheckCompleted 필드를 실제 게이트 로직에 활용할 예정이라면 해당 조건문(if (state.isForceUpdateCheckCompleted) { … })을 추가해주세요.
  • 별도 활용 계획이 없다면, 불필요한 상태 필드이므로 제거하여 코드 단순화 및 가독성 향상을 도모할 수 있습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt (1)

60-68: 나가기 동작에서 exitProcess 사용은 지양 권장

finishAffinity()만으로도 앱의 모든 액티비티 스택을 정리할 수 있습니다. exitProcess(0)는 프로세스 레벨 강제 종료로, 크래시/로그 수집 유실, 향후 리소스 정리 문제를 야기할 수 있습니다. 강제 업데이트 UX에도 불필요한 강도입니다.

아래처럼 exitProcess 호출을 제거해 주세요.

         ForceUpdateDialog(
             onUpdateClick = { openAppInPlayStore(activity) },
             onDismissRequest = {
                 activity?.finishAffinity()
-                exitProcess(0)
             },
         )

추가로, 업데이트 버튼 경로에서는 openAppInPlayStore가 성공 시 내부적으로 지연 종료(finishAppWithDelay)를 수행하므로 중복 종료 코드는 필요 없습니다.

domain/src/main/java/com/threegap/bitnagil/domain/version/usecase/CheckUpdateRequirementUseCase.kt (2)

6-11: 의도 명시를 위한 KDoc와 네이밍(선택) 보완 제안

반환형이 강제 업데이트 여부(Boolean)만 노출되므로, KDoc에 “강제 업데이트 필요 여부만 반환”을 명시하면 호출 측 가독성이 좋아집니다. 또한 선택적으로 클래스명을 CheckForceUpdateUseCase 등으로 좁혀도 의도가 더 분명해집니다.


9-11: CheckUpdateRequirementUseCase 단위 테스트 추가 권장

  • domain/src/main/java/com/threegap/bitnagil/domain/version/usecase/CheckUpdateRequirementUseCase.kt에 대응하는 테스트 클래스(CheckUpdateRequirementUseCaseTest)가 현재 존재하지 않습니다.
  • 아래 시나리오를 검증하는 단위 테스트를 작성해 주세요.
    • versionRepository.checkVersion() 호출 결과에서 isForced=true/false가 각각 올바르게 매핑되는지 확인
    • Result.failure가 반환될 때 예외가 전파되어 실패 상태가 유지되는지 확인
    • 레포지토리에서 예외를 던지지 않으므로 map { it.isForced }만으로 로직이 충분함을 보장
  • 테스트 파일 예시 위치:
    domain/src/test/java/com/threegap/bitnagil/domain/version/usecase/CheckUpdateRequirementUseCaseTest.kt
  • 원하시면 테스트 스켈레톤 코드를 제공해 드리겠습니다.
domain/src/main/java/com/threegap/bitnagil/domain/version/repository/VersionRepository.kt (1)

5-7: 도메인 인터페이스의 오류 모델링 명확화 제안

Result<UpdateRequirement> 사용 자체는 문제없지만, 도메인 계층에서 네트워크/서버/비즈니스 에러를 구분하고자 한다면 sealed interface DomainError 등을 도입해 오류 타입을 명시하는 것도 고려해볼 만합니다. 현재 스코프에서 필수는 아니고, 장기적 확장성을 위한 선택 제안입니다.

data/src/main/java/com/threegap/bitnagil/data/version/datasourceImpl/VersionDataSourceImpl.kt (2)

14-21: BuildConfig 직접 의존을 AppVersionProvider로 치환하여 테스트 용이성 개선 제안

현재 구현은 BuildConfig.VERSION_MAJOR/MINOR/PATCH에 직접 의존하여 단위 테스트에서 값을 주입하기 어렵습니다. 버전 정보를 제공하는 AppVersionProvider(예: fun major(): Int, minor(), patch())를 주입하면 테스트/모킹이 쉬워집니다.

적용 예(이 파일 내 변경 diff):

-class VersionDataSourceImpl @Inject constructor(
-    private val versionService: VersionService,
-) : VersionDataSource {
+class VersionDataSourceImpl @Inject constructor(
+    private val versionService: VersionService,
+    private val appVersionProvider: AppVersionProvider, // 새 의존성
+) : VersionDataSource {

     override suspend fun checkVersion(): Result<VersionCheckResponseDto> =
         safeApiCall {
             versionService.checkVersion(
-                majorVersion = BuildConfig.VERSION_MAJOR,
-                minorVersion = BuildConfig.VERSION_MINOR,
-                patchVersion = BuildConfig.VERSION_PATCH,
+                majorVersion = appVersionProvider.major(),
+                minorVersion = appVersionProvider.minor(),
+                patchVersion = appVersionProvider.patch(),
             )
         }

추가로, AppVersionProvider 인터페이스 및 기본 구현(실서버용으로 BuildConfig를 읽는 구현)은 별도 파일로 제공하면 됩니다.


1-1: 패키지 네이밍 컨벤션 정합성

datasourceImpl와 같이 대문자를 포함한 패키지/디렉터리명은 일반적인 Kotlin/Java 컨벤션(전부 소문자)에서 벗어납니다. data.version.datasource.impl로 분리하거나 data.version.datasource.internal 등으로 통일하는 것을 권장드립니다. 대체로 빌드에는 문제 없지만 유지보수성 측면에서 개선 여지가 있습니다.

data/src/main/java/com/threegap/bitnagil/data/version/repositoryImpl/VersionRepositoryImpl.kt (1)

9-11: 스코프/라이프사이클 명시(선택)

레포지토리가 무상태(stateless)라면 DI에서 @Singleton 스코프를 부여해도 좋습니다. 인스턴스 중복 생성을 방지하고 메모리 프로파일을 단순화합니다.

data/src/main/java/com/threegap/bitnagil/data/version/service/VersionService.kt (1)

8-15: 캐싱/백오프 전략(선택)

강제 업데이트 응답이 빈번하지 않다는 전제라면, OkHttp 캐시(짧은 max-age)나 ETag 기반 조건부 요청, 혹은 앱 단 TTL 캐시를 얹어 스플래시 체류 시간을 안정화할 수 있습니다. 네트워크 불안정 시 재시도(backoff) 정책도 함께 고려해 보세요.

원하시면 OkHttp Interceptor 기반의 간단한 TTL 캐시 예제를 제공하겠습니다.

data/src/main/java/com/threegap/bitnagil/data/version/model/response/VersionCheckResponseDto.kt (2)

7-11: DTO 필드 기본값 추가로 역직렬화 안전성 보강 제안

서버 스키마 변화(필드 누락)나 부분 배포 시를 고려해 Boolean 필드에 기본값을 두면 역직렬화 예외를 줄일 수 있습니다. 현재 상위 레이어가 실패 시 기본값(false)으로 진행하는 정책과도 일치합니다.

다음과 같이 기본값을 부여하는 것을 권장합니다:

 data class VersionCheckResponseDto(
     @SerialName("forceUpdateYn")
-    val forceUpdateYn: Boolean,
+    val forceUpdateYn: Boolean = false,
 )

13-16: 도메인 매핑 적절 — 가시성 축소 및 일관성 개선 소폭 제안

매핑 자체는 명확합니다. 이 확장함수는 data 모듈 내부에서만 쓰일 가능성이 높으므로 가시성을 internal로 줄이고, 한 줄 표현으로 간결화하면 가독성이 좋아집니다.

다음과 같이 정리해 보세요:

-fun VersionCheckResponseDto.toDomain() =
-    UpdateRequirement(
-        isForced = this.forceUpdateYn,
-    )
+internal fun VersionCheckResponseDto.toDomain(): UpdateRequirement =
+    UpdateRequirement(isForced = forceUpdateYn)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (3)

73-85: 타임아웃 매직 넘버 제거 및 재사용성 개선 제안

5초 타임아웃을 상수로 승격해 오토로그인과 공통 사용하면 유지보수가 수월합니다. 또한 실패/타임아웃 시 false로 fail-open하는 현재 정책은 PR 설명과 일치합니다.

다음과 같이 타임아웃 상수를 사용하도록 변경해 주세요:

-            val isUpdateRequired = withTimeoutOrNull(5000) {
+            val isUpdateRequired = withTimeoutOrNull(TIMEOUT_MS) {
                 checkUpdateRequirementUseCase().getOrElse { false }
             } ?: false

또한 클래스 내부(예: companion object)에 상수 추가를 권장합니다:

companion object {
    private const val TIMEOUT_MS = 5_000L
    private const val ANIMATION_RETRY_DELAY_MS = 100L
}

102-110: 애니메이션 완료 후 네비게이션 가드 강화(업데이트 체크 완료 여부 포함)

현재는 forceUpdateRequired만 즉시 리턴 가드하고, 오토로그인 완료 여부만 폴링합니다. 업데이트 체크가 아직 끝나지 않은 경우도 함께 기다리면 불필요한 폴링 횟수를 줄이고 의도를 더 분명히 할 수 있습니다.

다음과 같이 두 조건을 통합해 지연 재시도를 수행하는 것을 제안합니다:

-        if (splashState.forceUpdateRequired) return
-
-        if (!splashState.isAutoLoginCompleted) {
-            viewModelScope.launch {
-                delay(100)
-                onAnimationCompleted()
-            }
-            return
-        }
+        if (splashState.forceUpdateRequired) return
+
+        if (!splashState.isForceUpdateCheckCompleted || !splashState.isAutoLoginCompleted) {
+            viewModelScope.launch {
+                delay(ANIMATION_RETRY_DELAY_MS)
+                onAnimationCompleted()
+            }
+            return
+        }

73-85: 오류 가시성 확보(로그/추적) 고려

네트워크 실패/타임아웃 시 기본값(false)으로 진행하는 정책은 UX상 합리적입니다. 다만 운영 관점에서 원인 추적이 어려워질 수 있으니 최소한의 로깅(예: Timber.d/e) 또는 이벤트 트래킹을 추가하는 것을 권장합니다.

원하시면 간단한 로깅 포인트를 제안드리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 63deaf0 and 2b5155e.

📒 Files selected for processing (21)
  • app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt (2 hunks)
  • build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidApplicationPlugin.kt (2 hunks)
  • build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidLibraryPlugin.kt (2 hunks)
  • build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/version/datasource/VersionDataSource.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/version/datasourceImpl/VersionDataSourceImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/version/model/response/VersionCheckResponseDto.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/version/repositoryImpl/VersionRepositoryImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/version/service/VersionService.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/version/model/UpdateRequirement.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/version/repository/VersionRepository.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/version/usecase/CheckUpdateRequirementUseCase.kt (1 hunks)
  • gradle/libs.versions.toml (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt (4 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (5 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/component/template/ForceUpdateDialog.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashIntent.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashState.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/util/PlayStoreUtils.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T09:06:19.028Z
Learnt from: wjdrjs00
PR: YAPP-Github/Bitnagil-Android#101
File: presentation/src/main/java/com/threegap/bitnagil/presentation/setting/SettingViewModel.kt:61-67
Timestamp: 2025-08-13T09:06:19.028Z
Learning: In Android ViewModels, when logout success triggers navigation to a different screen (like NavigateToLogin), the current ViewModel's lifecycle ends, so loading states don't need to be explicitly reset in the success case since the ViewModel will be destroyed.

Applied to files:

  • presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt
🧬 Code graph analysis (6)
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidApplicationPlugin.kt (1)
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt (1)
  • configureAppVersion (10-37)
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidLibraryPlugin.kt (1)
build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt (1)
  • configureAppVersion (10-37)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/component/template/ForceUpdateDialog.kt (1)
  • ForceUpdateDialog (25-93)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/util/PlayStoreUtils.kt (1)
  • openAppInPlayStore (16-27)
data/src/main/java/com/threegap/bitnagil/data/version/datasourceImpl/VersionDataSourceImpl.kt (1)
data/src/main/java/com/threegap/bitnagil/data/common/SafeApiCall.kt (1)
  • safeApiCall (10-25)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/component/template/ForceUpdateDialog.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
  • BitnagilTextButton (34-85)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (1)
  • sendIntent (30-37)
⏰ 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: build
🔇 Additional comments (21)
gradle/libs.versions.toml (1)

9-11: SemVer(major/minor/patch)로의 분리, 방향성 좋습니다.

버전 관리가 명확해지고, AppVersion 확장에서 일관되게 소비하기 용이해졌습니다.

build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/AppVersion.kt (1)

10-23: 버전 계산/주입 로직의 중앙집중화: 전반적으로 좋습니다.

앱/라이브러리 양쪽에 동일한 규칙으로 주입되어 일관성이 생겼고, 강제 업데이트 로직에서도 안정적으로 참조할 수 있겠습니다.

build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidApplicationPlugin.kt (2)

25-25: configureAppVersion() 연결: 잘 붙였습니다.

버전 주입 지점을 한 곳으로 모으는 방향이 명확합니다.


21-29: 중복 정의 없음—검증 완료

Gradle 스크립트 내에서 versionName 또는 versionCode를 명시적으로 할당한 위치가 없으며, BuildConfig를 통한 버전 조회(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)도 의도된 로직이므로 별도 중복 정의는 없습니다.
추가 조치 없이 해당 리뷰 코멘트는 해결된 것으로 보입니다.

build-logic/convention/src/main/java/com/threegap/bitnagil/convention/AndroidLibraryPlugin.kt (1)

21-21: configureAppVersion() 호출: 라이브러리 측 배선도 OK

라이브러리도 동일 규칙으로 BuildConfig가 생성되어 소비 측에서 일관되게 접근할 수 있습니다.

app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt (2)

63-65: VersionDataSource 바인딩 추가 LGTM

DI 그래프에 VersionDataSourceImplVersionDataSource 바인딩이 정상적으로 추가되었습니다. 스코프도 @Singleton으로 일관됩니다.


17-19: DI 설정 검증 완료

  • VersionDataSourceImpl 클래스에 @Inject 생성자 존재 확인 (data/src/main/java/com/threegap/bitnagil/data/version/datasourceImpl/VersionDataSourceImpl.kt:10)
  • bindVersionDataSource 바인딩이 한 번만 정의되어 중복 없음 확인 (app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt:65)
  • VersionRepositoryImpl@Inject 생성자 및 bindVersionRepository 등록 확인 (data/src/main/java/com/threegap/bitnagil/data/version/repositoryImpl/VersionRepositoryImpl.kt:9, app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt:59)
  • provideVersionService 제공자 등록도 정상적으로 존재 확인 (app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt:67)

위 항목 모두 올바르게 설정되어 있어 추가 조치가 필요하지 않습니다.

app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt (1)

57-59: VersionRepositoryImpl @Inject 생성자 및 중복 바인딩 확인 완료

VersionRepositoryImpl 클래스에 @Inject 생성자가 제대로 적용되어 있으며, RepositoryModule.kt(59번 줄)에서 중복 바인딩도 존재하지 않음을 확인했습니다. 다른 레포지토리 바인딩과 일관된 구성이므로 바로 승인합니다.

app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt (1)

65-68: VersionService 인터페이스에 인증 헤더 미사용 확인 필요

VersionService의 checkVersion 메서드는 @get만 선언되어 있을 뿐, @Header@headers 어노테이션이 없어 인터페이스 차원에서 인증 토큰이 전송되지 않습니다.
따라서 서버 엔드포인트가 정말로 인증 없이 허용되는지 API 명세 또는 백엔드 팀 확인이 필요합니다.

• 확인 위치

  • 파일: data/src/main/java/com/threegap/bitnagil/data/version/service/VersionService.kt
  • 메서드 선언 (약 8–12행)
    interface VersionService {
        @GET("/api/v1/version/android/check")
        suspend fun checkVersion(
            @Query("major") majorVersion: Int,
            @Query("minor") minorVersion: Int,
            @Query("patch") patchVersion: Int
        ): VersionResponse
    }

• 다음 중 하나로 인증 필요 여부 확정

  1. API 명세서/문서 확인
  2. 서버 사이드 로그 혹은 백엔드 팀에 직접 문의
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt (2)

43-46: Activity 참조 캐스팅은 안전하지만, 미리 널 가드/테스트 친화성 고려

val activity = context as? ComponentActivity는 합리적입니다. 다만 미리보기(Preview)나 특정 Context 래핑 상황에서 null 가능성이 있으므로, openAppInPlayStore 호출부처럼 널 세이프티를 유지하는 현재 방식은 적절합니다. 별도 액션은 필요 없습니다.


60-68: 강제 업데이트 다이얼로그 표시는 적절합니다. Back/Outside 차단도 OK

BasicAlertDialog에서 dismissOnBackPress = false, dismissOnClickOutside = false 설정이 되어 있어 의도대로 강제 플로우를 보장합니다. 접근성 측면에서 버튼 라벨(나가기/업데이트)도 명확합니다.

domain/src/main/java/com/threegap/bitnagil/domain/version/usecase/CheckUpdateRequirementUseCase.kt (1)

9-11: 성공/실패 전파와 Boolean 매핑이 간결하고 정확합니다.

Result<UpdateRequirement>Result<Boolean>으로 깔끔하게 매핑하고, 실패는 그대로 전파합니다. 스플래시 플로우에 적합한 얇은 유즈케이스로 보여요.

domain/src/main/java/com/threegap/bitnagil/domain/version/repository/VersionRepository.kt (1)

5-7: DI 바인딩 정상 확인됨

앱 모듈(app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt)에 VersionRepositoryVersionRepositoryImpl 간의 @Binds 바인딩이 이미 추가되어 있어, 런타임 주입 오류가 발생하지 않습니다.
RepositoryModule.kt 59번째 줄:

@Binds
@Singleton
abstract fun bindVersionRepository(versionRepositoryImpl: VersionRepositoryImpl): VersionRepository

추가 조치 불필요합니다.

data/src/main/java/com/threegap/bitnagil/data/version/datasourceImpl/VersionDataSourceImpl.kt (2)

14-21: 서버 계약 검증: forceUpdateYn 필드 존재 확인됨

  • VersionCheckResponseDtoforceUpdateYn: Boolean 필드가 정의되어 있어, 서버가 강제 업데이트 여부를 본문으로 전달함을 확인했습니다.

추가 검증 포인트

  • 서버가 forceUpdateYn 외에 다른 업데이트 관련 필드를 응답에 포함시키지 않는지,
  • 특정 상황(예: “업데이트 불필요”)에 빈 객체 혹은 null을 반환하지 않는지,
  • versionService.checkVersion 호출 시 HTTP 200 외 에러 코드는 모두 예외로 처리되는지

위 내용이 서버 계약과 일치한다면, 현재 safeApiCall 사용 방식으로 충분합니다.


17-20: data 모듈에 BuildConfig.VERSION_* 필드 정의 확인 필요

build-logic/convention 플러그인(AppVersion.kt)에서 VERSION_MAJOR, VERSION_MINOR, VERSION_PATCHbuildConfigField로 주입하도록 설정되어 있습니다. 다만, 이 설정이 적용되려면 데이터 모듈이 해당 플러그인(라이브러리 확장)을 반드시 사용해야 합니다. 그렇지 않으면 컴파일 시 BuildConfig.VERSION_MAJOR 등이 정의되지 않아 에러가 발생할 수 있습니다.

점검 항목:

  • data/build.gradle.kts (또는 data/build.gradle)에
    plugins { id("com.threegap.bitnagil.convention") }
    혹은
    apply(plugin = "com.threegap.bitnagil.convention")
    와 같이 convention 플러그인이 적용되어 있는지
  • 위 플러그인 적용으로 libExtension.defaultConfig { buildConfigField("int", "VERSION_MAJOR", ...) } 블록이 활성화되는지

확인 후 결과를 공유해 주세요.

data/src/main/java/com/threegap/bitnagil/data/version/repositoryImpl/VersionRepositoryImpl.kt (1)

13-15: DTO→도메인 매핑 경로 단순하고 명확합니다.

versionDataSource.checkVersion().map { it.toDomain() }로 성공 경로만 변환하고 실패는 그대로 전파하는 전략이 일관적입니다. 이후 다른 호출부에서도 동일 패턴을 유지하면 가독성이 좋아집니다.

data/src/main/java/com/threegap/bitnagil/data/version/service/VersionService.kt (1)

9-14: 선행 슬래시(/) 경로 사용 점검 및 제거 권장

Retrofit에서 @GET("/…")처럼 선행 슬래시를 사용하면 baseUrl의 path 세그먼트를 무시하고 루트 경로(/)부터 요청을 구성합니다.
현재 NetworkModule에서 BuildConfig.BASE_URL(→ bitnagil.dev.url / bitnagil.prod.url)을 그대로 사용 중이므로, 이 값이 도메인 루트(예: https://api.example.com/)만 포함하는지 검증해 주세요. 만약 path 세그먼트가 없다면, 선행 슬래시를 제거하여 상대 경로로 사용하는 것이 더 안전합니다.

• 점검 대상

  • data/src/main/java/com/threegap/bitnagil/data/version/service/VersionService.kt
    • 기존: @GET("/api/v1/version/android/check")
    • 변경 제안: @GET("api/v1/version/android/check")

BASE_URL 확인 방법

  • app/build.gradle.kts에서 bitnagil.dev.url, bitnagil.prod.url 값이 정확히 어떤 URL을 가리키는지 확인
    rg -n "bitnagil.(dev|prod).url" -C3 app/build.gradle.kts
  • 필요시 gradle.properties나 환경 변수 설정 파일을 열어 실제 값 점검
data/src/main/java/com/threegap/bitnagil/data/version/model/response/VersionCheckResponseDto.kt (1)

7-11: YN 네이밍과 Boolean 타입 정합성 반드시 확인 필요
forceUpdateYn 필드를 Boolean으로 역직렬화하고 있으므로, 백엔드가 실제로 true/false JSON Boolean을 반환하는지 반드시 검증해주세요. 만약 "Y"/"N" 문자열을 반환할 경우 런타임 역직렬라이제이션 오류가 발생합니다.

다음 사항을 확인하시기 바랍니다:

  • API 사양서(OpenAPI/Swagger 등)에 정의된 forceUpdateYn 스키마가 boolean인지 점검
  • 실제 엔드포인트를 cURL/Postman 등으로 호출해 반환 값을 직접 확인
  • 백엔드 담당자나 API 문서화 도구에서 해당 필드 타입이 변경된 이력이 없는지 검토
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (3)

29-31: 스플래시 진입 시 강제 업데이트 선행 체크 — 의도 적합

앱 시작 시 강제 업데이트를 우선 확인하는 플로우는 명확합니다. 이후 오토로그인으로 분기하는 현재 설계와 잘 맞습니다.


45-51: 상태 전이 명확 — 완료 플래그 관리 적절

forceUpdateRequired와 isForceUpdateCheckCompleted를 함께 세팅하여 이후 플로우 가드를 단순화한 점 좋습니다. UI에서 다이얼로그 표시 조건으로 사용하기 적합합니다.


7-7: DI 구성 확인 완료: UseCase 자동 주입 지원 및 모듈 바인딩 완비

  • CheckUpdateRequirementUseCase.kt 클래스에 @Inject constructor가 적용되어 있어 Hilt가 자동으로 프로비전합니다.
  • DataSourceModule.kt에서 VersionDataSource가, RepositoryModule.kt에서 VersionRepository가 각각 @Binds로 이미 등록되어 있습니다.

위 사항으로 보아 새로운 UseCase 의존성 주입에 필요한 추가 모듈 바인딩은 없습니다.

Copy link
Contributor

@l5x5l l5x5l left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 머지 진행하시죠!

@wjdrjs00 wjdrjs00 merged commit 3397f0e into develop Aug 21, 2025
2 checks passed
@wjdrjs00 wjdrjs00 deleted the feature/#116-app-force-update branch August 21, 2025 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature 새로운 기능 구현 🧤 대현
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] 앱 강제 업데이트 로직을 구현합니다.
2 participants