Skip to content

Conversation

@etama123
Copy link
Contributor

@etama123 etama123 commented Jan 18, 2026

#️⃣ 이슈 번호

#35


🛠️ 작업 내용

  • 기존 스플래시 화면을 Android 12 공식 core-splashscreen API와 Compose 기반으로 마이그레이션했습니다.
  • SplashUiState를 추가하여 SplashViewModel에서 상태를 관리하도록 했습니다.
  • LaunchedEffect를 통해 상태 변화를 감지하고, 저장된 축제 ID 유무에 따라 메인 화면 또는 탐색 화면으로의 자동 전환을 처리했습니다.

🙇🏻 중점 리뷰 요청

  • 특히 확인이 필요한 부분, 고민했던 부분 등을 적어주세요.

📸 이미지 첨부 (Optional)

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 앱 시작 시 업데이트 알림 대화상자 추가
    • 네트워크 오류 대화상자 추가
  • 개선 사항

    • 스플래시 화면의 시각적 테마 업데이트
    • 앱 시작 중 사용자 메시지 개선 및 톤 조정
    • 스플래시 화면 UI 상태 관리 최적화

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

@etama123 etama123 self-assigned this Jan 18, 2026
@etama123 etama123 added the Feat label Jan 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

개요

스플래시 화면을 Compose 기반 UI 상태 흐름으로 마이그레이션했습니다. 기존의 명령형 옵저버 기반 네비게이션을 Kotlin Flow와 StateFlow를 활용한 선언형 상태 관리로 전환하고, 새로운 대화상자 컴포넌트를 추가하며 테마 설정을 업데이트했습니다.

변경 사항

변경 항목 / 파일 요약
Manifest 및 Theme 설정
app/src/main/AndroidManifest.xml, app/src/main/res/values/themes.xml
SplashActivity의 테마를 Theme.Festabook.Splash로 변경하고, 이 테마를 Material SplashScreen 기반으로 재구성. windowSplashScreenBackground, windowSplashScreenAnimatedIcon, postSplashScreenTheme 속성 추가
스플래시 UI 상태 정의
app/src/main/java/com/daedan/festabook/presentation/splash/SplashUiState.kt
Loading, ShowUpdateDialog, ShowNetworkErrorDialog, NavigateToMain(festivalId), NavigateToExplore 상태를 가진 새로운 sealed interface SplashUiState 추가
액티비티 및 ViewModel 마이그레이션
app/src/main/java/com/daedan/festabook/presentation/splash/SplashActivity.kt, app/src/main/java/com/daedan/festabook/presentation/splash/SplashViewModel.kt
LiveData 기반 navigation에서 StateFlow 기반 uiState로 전환. SplashActivity는 Compose 기반으로 collectAsStateWithLifecycle을 사용하여 상태 감시. handleVersionCheckResult() 메서드 추가로 버전 확인 결과 처리
대화상자 컴포넌트
app/src/main/java/com/daedan/festabook/presentation/splash/component/SplashDialogs.kt
UpdateDialog, NetworkErrorDialog 컴포넌트 추가 (각각 SplashInfoDialog로 구현)
문자열 및 테스트 업데이트
app/src/main/res/values/strings.xml, app/src/test/java/com/daedan/festabook/splash/SplashViewModelTest.kt, app/src/test/java/com/daedan/festabook/FlowExtension.kt
스플래시 메시지 톤 조정 및 마침표 추가. NavigationState 대신 SplashUiState 사용으로 테스트 업데이트. 불필요한 FlowExtension 테스트 유틸리티 제거

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~50분

연관 이슈

연관 PR

  • 전역 Ktlint 적용 및 기타 작업 수행 #15: 동일한 SplashViewModel 클래스를 수정하는 PR로, 기존에는 @Inject 어노테이션 위치를 조정했으나 본 PR에서는 해당 클래스의 전체 API 및 상태 관리 패턴(SplashUiState 도입, handleVersionCheckResult 추가)을 재구성하므로 관련성이 있습니다.

제안 검토자

  • oungsi2000
  • parkjiminnnn

🐰 스플래시 화면이 Compose로 춤을 추고,
StateFlow의 우아한 상태들이 흘러가네,
대화상자는 예쁘게 피어나고,
낡은 LiveData는 물러가고,
새로운 상태 기계가 우리 앱을 인도합니다! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 Splash 화면을 Compose로 마이그레이션하는 변경사항을 명확하게 요약하고 있으며, 제공된 파일 변경사항들과 일치합니다.

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


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.

@etama123
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/daedan/festabook/presentation/splash/SplashViewModel.kt`:
- Around line 36-44: checkFestivalId currently calls
festivalLocalDataSource.getFestivalId() on the main thread which can block UI;
move the SharedPreferences read onto an IO dispatcher: wrap the call in a
coroutine (e.g., viewModelScope.launch(Dispatchers.IO) or using
withContext(Dispatchers.IO)) inside SplashViewModel.checkFestivalId, read
festivalId on IO, then post the result back to the main thread before updating
_uiState (ensure you set _uiState.value = SplashUiState.NavigateToExplore or
SplashUiState.NavigateToMain(festivalId) from the main thread after the IO
read).

In `@app/src/main/res/values/strings.xml`:
- Around line 151-154: The string update_notice_message contains "\n " (a
newline followed by a space) which can create an unintended indent; update the
value for update_notice_message to remove the space after the newline so it uses
"\n" (or replace the newline+space with an explicit non-breaking space or
desired spacing) to ensure no accidental leading whitespace is rendered.
🧹 Nitpick comments (4)
app/src/main/java/com/daedan/festabook/presentation/splash/SplashActivity.kt (2)

26-34: 업데이트 취소/실패 분기 UX를 한 번 더 확인해 주세요
RESULT_OK 외의 모든 케이스가 실패로 처리되어 사용자가 업데이트를 취소해도 네트워크 오류 다이얼로그로 보일 수 있습니다. 의도된 동작인지 확인하고 필요 시 분기 처리를 고려해 주세요.


51-64: 업데이트 체크 지연 시 스플래시가 무기한 유지될 수 있습니다
Loading 동안 OS 스플래시를 유지하고 있는데, getIsAppUpdateAvailable()가 지연/정지되면 화면이 계속 멈춰 있을 수 있어요. 호출에 타임아웃/실패 처리가 있는지 확인해 주세요.

⏱️ 예시: 타임아웃 추가
+import kotlinx.coroutines.withTimeoutOrNull
...
-                LaunchedEffect(Unit) {
-                    // 앱 실행 시 즉시 앱 버전 업데이트의 필요 유무 확인
-                    val result = appVersionManager.getIsAppUpdateAvailable()
-                    viewModel.handleVersionCheckResult(result)
-                }
+                LaunchedEffect(Unit) {
+                    // 앱 실행 시 즉시 앱 버전 업데이트의 필요 유무 확인
+                    val result = withTimeoutOrNull(3_000) {
+                        appVersionManager.getIsAppUpdateAvailable()
+                    }
+                    viewModel.handleVersionCheckResult(
+                        result ?: Result.failure(Exception("Update check timeout")),
+                    )
+                }
app/src/main/java/com/daedan/festabook/presentation/splash/component/SplashDialogs.kt (1)

54-115: Dialog 비닫힘 의도를 명시하면 더 명확합니다
현재 onDismissRequest를 빈 람다로 두어 닫힘을 막고 있는데, DialogProperties로 명시하면 의도가 더 분명해집니다.

🧩 제안 수정안
+import androidx.compose.ui.window.DialogProperties
...
-    Dialog(onDismissRequest = {}) {
+    Dialog(
+        onDismissRequest = {},
+        properties = DialogProperties(
+            dismissOnBackPress = false,
+            dismissOnClickOutside = false,
+        ),
+    ) {
app/src/test/java/com/daedan/festabook/splash/SplashViewModelTest.kt (1)

43-94: 비동기 상태 갱신 시 테스트가 플러시를 필요로 할 수 있어요
handleVersionCheckResult가 viewModelScope에서 갱신된다면 assertion 전에 dispatcher를 advanceUntilIdle/runCurrent로 플러시해야 간헐 실패를 피할 수 있습니다. 또한 FestivalLocalDataSource.getFestivalId가 suspend 함수라면 coEvery 사용이 필요합니다. 시그니처를 확인해 주세요.

🧪 예시: dispatcher 플러시
-            splashViewModel.handleVersionCheckResult(updateResult)
+            splashViewModel.handleVersionCheckResult(updateResult)
+            testDispatcher.scheduler.advanceUntilIdle()

Comment on lines 36 to 44
private fun checkFestivalId() {
val festivalId = festivalLocalDataSource.getFestivalId()
Timber.d("festival ID : $festivalId")
Timber.d("현재 접속중인 festival ID : $festivalId")

if (festivalId == null) {
_navigationState.setValue(NavigationState.NavigateToExplore)
_uiState.value = SplashUiState.NavigateToExplore
} else {
_navigationState.setValue(NavigationState.NavigateToMain(festivalId))
_uiState.value = SplashUiState.NavigateToMain(festivalId)
}
Copy link

@coderabbitai coderabbitai bot Jan 18, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# FestivalLocalDataSource와 getFestivalId 구현 확인
rg -n --type=kt -C3 'class\s+FestivalLocalDataSource|interface\s+FestivalLocalDataSource'

Repository: festabook/android

Length of output: 87


🏁 Script executed:

#!/bin/bash
# getFestivalId 메서드 구현 확인
rg -n --type=kt -C5 'fun\s+getFestivalId\s*\('

Repository: festabook/android

Length of output: 87


🏁 Script executed:

#!/bin/bash
# FestivalLocalDataSource 파일 위치 찾기
fd -t f FestivalLocalDataSource

Repository: festabook/android

Length of output: 240


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/data/datasource/local/FestivalLocalDataSource.kt

Repository: festabook/android

Length of output: 322


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/data/datasource/local/FestivalLocalDataSourceImpl.kt

Repository: festabook/android

Length of output: 1647


🏁 Script executed:

cat -n app/src/main/java/com/daedan/festabook/presentation/splash/SplashViewModel.kt

Repository: festabook/android

Length of output: 1994


SharedPreferences I/O로 인한 메인 스레드 블로킹 위험을 반드시 해결하세요.

getFestivalId()는 SharedPreferences의 동기 연산(getLong())을 사용하므로 메인 스레드를 블로킹합니다. 특히 첫 실행 시 디스크에서 읽기가 발생할 수 있어 스플래시 화면에서 UI 지연이 발생할 수 있습니다. 아래와 같이 IO 디스패처로 이동해 주세요.

🔧 수정 예시
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 ...
-    private fun checkFestivalId() {
-        val festivalId = festivalLocalDataSource.getFestivalId()
-        Timber.d("현재 접속중인 festival ID : $festivalId")
-
-        if (festivalId == null) {
-            _uiState.value = SplashUiState.NavigateToExplore
-        } else {
-            _uiState.value = SplashUiState.NavigateToMain(festivalId)
-        }
-    }
+    private fun checkFestivalId() {
+        viewModelScope.launch(Dispatchers.IO) {
+            val festivalId = festivalLocalDataSource.getFestivalId()
+            Timber.d("현재 접속중인 festival ID : $festivalId")
+            _uiState.value =
+                if (festivalId == null) SplashUiState.NavigateToExplore
+                else SplashUiState.NavigateToMain(festivalId)
+        }
+    }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/daedan/festabook/presentation/splash/SplashViewModel.kt`
around lines 36 - 44, checkFestivalId currently calls
festivalLocalDataSource.getFestivalId() on the main thread which can block UI;
move the SharedPreferences read onto an IO dispatcher: wrap the call in a
coroutine (e.g., viewModelScope.launch(Dispatchers.IO) or using
withContext(Dispatchers.IO)) inside SplashViewModel.checkFestivalId, read
festivalId on IO, then post the result back to the main thread before updating
_uiState (ensure you set _uiState.value = SplashUiState.NavigateToExplore or
SplashUiState.NavigateToMain(festivalId) from the main thread after the IO
read).

Copy link
Contributor

Choose a reason for hiding this comment

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

토끼야 날카로웠다?

Copy link

Choose a reason for hiding this comment

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

@parkjiminnnn 감사합니다! 😊 스플래시 화면에서 UI 블로킹이 발생하지 않도록 SharedPreferences 읽기를 IO 디스패처로 옮기시면 더 부드러운 사용자 경험을 제공하실 수 있을 거예요.

Comment on lines +151 to +154
<string name="update_notice_message">새로운 버전이 출시되었어요.\n 더 나은 사용을 위해 앱을 업데이트해 주세요.</string>
<string name="update_notice_confirm">업데이트</string>
<string name="update_failed_title">서버 통신 실패</string>
<string name="update_failed_message">앱 서버에 접속할 수 없습니다. 다시 시도해주세요</string>
<string name="update_failed_message">앱 서버에 접속할 수 없습니다. 다시 시도해주세요.</string>
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

줄바꿈 뒤 선행 공백으로 들여쓰기 발생 가능
update_notice_message의 \n 뒤 공백이 의도치 않은 들여쓰기를 만들 수 있어요. 의도한 여백이 아니라면 공백을 제거해 주세요.

✂️ 제안 수정안
-    <string name="update_notice_message">새로운 버전이 출시되었어요.\n 더 나은 사용을 위해 앱을 업데이트해 주세요.</string>
+    <string name="update_notice_message">새로운 버전이 출시되었어요.\n더 나은 사용을 위해 앱을 업데이트해 주세요.</string>
📝 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
<string name="update_notice_message">새로운 버전이 출시되었어요.\n 더 나은 사용을 위해 앱을 업데이트해 주세요.</string>
<string name="update_notice_confirm">업데이트</string>
<string name="update_failed_title">서버 통신 실패</string>
<string name="update_failed_message">앱 서버에 접속할 수 없습니다. 다시 시도해주세요</string>
<string name="update_failed_message">앱 서버에 접속할 수 없습니다. 다시 시도해주세요.</string>
<string name="update_notice_message">새로운 버전이 출시되었어요.\n더 나은 사용을 위해 앱을 업데이트해 주세요.</string>
<string name="update_notice_confirm">업데이트</string>
<string name="update_failed_title">서버 통신 실패</string>
<string name="update_failed_message">앱 서버에 접속할 수 없습니다. 다시 시도해주세요.</string>
🤖 Prompt for AI Agents
In `@app/src/main/res/values/strings.xml` around lines 151 - 154, The string
update_notice_message contains "\n " (a newline followed by a space) which can
create an unintended indent; update the value for update_notice_message to
remove the space after the newline so it uses "\n" (or replace the newline+space
with an explicit non-breaking space or desired spacing) to ensure no accidental
leading whitespace is rendered.

Copy link
Contributor

@oungsi2000 oungsi2000 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 타마~~

Copy link
Contributor

@parkjiminnnn parkjiminnnn left a comment

Choose a reason for hiding this comment

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

고생하셧씁니다 타마
코드를 보니 신경을 많이 써주신 것 같네요!
코멘트 몇개만 확인해주세용

Comment on lines 36 to 44
private fun checkFestivalId() {
val festivalId = festivalLocalDataSource.getFestivalId()
Timber.d("festival ID : $festivalId")
Timber.d("현재 접속중인 festival ID : $festivalId")

if (festivalId == null) {
_navigationState.setValue(NavigationState.NavigateToExplore)
_uiState.value = SplashUiState.NavigateToExplore
} else {
_navigationState.setValue(NavigationState.NavigateToMain(festivalId))
_uiState.value = SplashUiState.NavigateToMain(festivalId)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

토끼야 날카로웠다?

Copy link
Contributor

Choose a reason for hiding this comment

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

verify 추가해주세요!

@DrawableRes iconResId: Int? = null,
onConfirm: () -> Unit,
) {
Dialog(onDismissRequest = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

DialogProperties(
        dismissOnBackPress = false,
        dismissOnClickOutside = false
    )

다음 코드를 추가해서 안전하게 다이어로그가 안닫히게 하는건 어떠신가용?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그 부분은 생각하지 못했네요! 바로 적용했습니다~
feat(Splash): 스플래시 다이얼로그 외부 클릭 및 뒤로가기 방지

@etama123 etama123 merged commit 66fb85e into develop Jan 20, 2026
3 checks passed
@etama123 etama123 deleted the feat/35 branch January 20, 2026 16:39
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.

4 participants