-
Notifications
You must be signed in to change notification settings - Fork 0
[Refactor/#138] MviViewModel 사용 부분을 Orbit으로 교체 #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughMviViewModel 기반 MVI 아키텍처에서 Orbit MVI로 마이그레이션하는 대규모 리팩토링입니다. 기존 MviIntent, MviSideEffect, MviState, MviViewModel 기본 클래스를 제거하고, 11개의 화면/ViewModel에서 Orbit의 ContainerHost와 ViewModel을 직접 사용하도록 변경하며, 기존 Intent 기반 상태 관리를 직접 메서드 호출로 대체합니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 추가 검토 사항:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
300-301: 빈 오류 처리 블록 개선 필요
onFailure블록이 비어있어 오류 발생 시 사용자에게 피드백이 없습니다. 최소한 로깅이나 토스트 메시지 표시를 고려해주세요.다음과 같이 오류 처리를 추가할 수 있습니다:
onFailure = { + postSideEffect(OnBoardingSideEffect.ShowToast(message = it.message ?: "오류가 발생했습니다.")) },Also applies to: 357-359
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)
55-82: Fix loading flag handling inloadRoutineandloadRecommendRoutineonFailure blocksTwo items to address:
- Missing loading flag reset on failure
BothloadRoutine(line 121–123) andloadRecommendRoutine(line 153–155) setloading = trueon entry but have emptyonFailureblocks. If the network call fails, the UI remains indefinitely in loading state. Addloading = falsein bothonFailureblocks:onFailure = { reduce { state.copy(loading = false) } },
- Nested intent pattern in
initResource
initResourceis itself anintent { ... }block but callsloadRoutine/loadRecommendRoutine(alsointent { ... }functions) at lines 66 and 78. While functional, this queues multiple intent blocks sequentially rather than executing a single unified intent. Consider refactoringloadRoutineandloadRecommendRoutineinto internalsuspendhelper functions and consolidating their logic into a singleintent { ... }block withininitResourcefor better alignment with Orbit patterns.
🧹 Nitpick comments (9)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (1)
49-51: 예외가 무시되고 있습니다.자동 로그인 실패 시 예외를 catch하지만 로깅하지 않아 디버깅이 어려울 수 있습니다. 최소한 로그를 남기는 것을 권장합니다.
} catch (e: Exception) { + // TODO: Consider logging the exception for debugging + // Log.w(TAG, "Auto login failed", e) reduce { state.copy(userRole = null, isAutoLoginCompleted = true) } }presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt (1)
91-109:reduce블록 내 부수 효과 분리를 권장합니다.Line 97에서
recommendRoutines프로퍼티 할당이reduce블록 내부에서 이루어지고 있습니다.reduce는 순수 상태 변환만 담당하는 것이 Orbit의 권장 패턴입니다.private fun loadRecommendRoutines() { intent { reduce { state.copy(isLoading = true) } fetchRecommendRoutinesUseCase().fold( onSuccess = { + recommendRoutines = it.toUiModel() reduce { - recommendRoutines = it.toUiModel() state.copy( isLoading = false, currentRoutines = getCurrentRoutines(state.selectedCategory, state.selectedRecommendLevel), emotionMarbleType = recommendRoutines.emotionMarbleType, ) } }, onFailure = { reduce { state.copy(isLoading = false) } }, ) } }presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (2)
65-87:intent {}블록 내viewModelScope.launch중첩 사용 검토 필요
intent {}블록 내에서viewModelScope.launch를 사용하면, intent 블록이 완료된 후에도 launch된 코루틴이 독립적으로 실행됩니다. 기술적으로 동작하지만, GuideViewModel 등 다른 마이그레이션된 ViewModel과 패턴이 다릅니다.
delay()사용이 필요한 경우, intent 블록 자체가 suspend context를 제공하므로 직접 호출이 가능합니다:fun selectEmotion(emotionType: String, minimumDelay: Long = 0) = intent { val isLoading = state.isLoading if (isLoading) return@intent reduce { state.copy( isLoading = true, showLoadingView = true, ) } - viewModelScope.launch { - if (minimumDelay > 0) { - delay(minimumDelay) - } + if (minimumDelay > 0) { + delay(minimumDelay) + } - registerEmotionUseCase(emotionType = emotionType).fold( - onSuccess = { emotionRecommendRoutines -> - val recommendRoutines = emotionRecommendRoutines.map { EmotionRecommendRoutineUiModel.fromEmotionRecommendRoutine(it) } - reduce { - state.copy( - recommendRoutines = recommendRoutines, - step = EmotionScreenStep.RecommendRoutines, - isLoading = false, - showLoadingView = false, - ) - } - }, - onFailure = { - postSideEffect(EmotionSideEffect.ShowToast(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요.")) - postSideEffect(EmotionSideEffect.NavigateToBack) - }, - ) - } + registerEmotionUseCase(emotionType = emotionType).fold( + onSuccess = { emotionRecommendRoutines -> + val recommendRoutines = emotionRecommendRoutines.map { EmotionRecommendRoutineUiModel.fromEmotionRecommendRoutine(it) } + reduce { + state.copy( + recommendRoutines = recommendRoutines, + step = EmotionScreenStep.RecommendRoutines, + isLoading = false, + showLoadingView = false, + ) + } + }, + onFailure = { + postSideEffect(EmotionSideEffect.ShowToast(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요.")) + postSideEffect(EmotionSideEffect.NavigateToBack) + }, + ) }
121-133:registerRecommendRoutines에서도 동일한 패턴 적용 가능위
selectEmotion과 마찬가지로viewModelScope.launch중첩을 제거하여 일관성을 유지할 수 있습니다.fun registerRecommendRoutines() = intent { val isLoading = state.isLoading if (isLoading) return@intent - viewModelScope.launch { - reduce { state.copy(isLoading = true) } + reduce { state.copy(isLoading = true) } - val selectedRecommendRoutineIds = state.recommendRoutines.filter { it.selected }.map { it.id } - registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds).fold( - onSuccess = { - postSideEffect(EmotionSideEffect.NavigateToBack) - }, - onFailure = { - reduce { state.copy(isLoading = false) } - }, - ) - } + val selectedRecommendRoutineIds = state.recommendRoutines.filter { it.selected }.map { it.id } + registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds).fold( + onSuccess = { + postSideEffect(EmotionSideEffect.NavigateToBack) + }, + onFailure = { + reduce { state.copy(isLoading = false) } + }, + ) }presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (1)
274-304: loadRecommendRoutines 패턴 일관성 검토 권장이 함수는
intent블록을 사용하지 않고viewModelScope.async를 직접 사용합니다. 다른 함수들은 모두intent블록을 사용하는데, 이 함수만 예외입니다.현재 구현도 동작하지만(job 취소 로직 포함), 패턴 일관성을 위해 다음 중 하나를 고려해볼 수 있습니다:
- 전체를
intent블록으로 감싸고 내부에서 async 사용- 현재대로 유지하되 주석으로 의도 명시
presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt (1)
78-84: Orbit 패턴과의 일관성을 위해intent/subIntent사용을 고려해 보세요.현재
viewModelScope.launch를 사용하고 있는데,HomeViewModel의observeWriteRoutineEvent등에서는subIntent를 사용하여 Flow를 수집합니다. 기능적으로는 동일하게 동작하지만, 프로젝트 전반의 일관성을 위해 Orbit DSL 사용을 고려해 볼 수 있습니다.private fun observeRoutineChanges() { - viewModelScope.launch { - getWriteRoutineEventFlowUseCase().collect { - fetchRoutines() + intent { + getWriteRoutineEventFlowUseCase().collect { + fetchRoutines() + } } - } }presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementScreen.kt (1)
105-122: 선택적 개선: 불필요한 람다 래핑을 제거할 수 있습니다.Lines 105, 113, 121에서
onCheckedChange = { onToggleTermsOfService() }형태로 람다를 사용하고 있는데,onCheckedChange = onToggleTermsOfService처럼 직접 전달하는 것이 더 간결합니다.다음과 같이 수정할 수 있습니다:
TermsAgreementItem( title = "(필수) 서비스 이용약관 동의", - onCheckedChange = { onToggleTermsOfService() }, + onCheckedChange = onToggleTermsOfService, isChecked = uiState.agreedTermsOfService, showMore = true, onClickShowMore = onShowTermsOfService, ) TermsAgreementItem( title = "(필수) 개인정보 수집·이용 동의", - onCheckedChange = { onTogglePrivacyPolicy() }, + onCheckedChange = onTogglePrivacyPolicy, isChecked = uiState.agreedPrivacyPolicy, showMore = true, onClickShowMore = onShowPrivacyPolicy, ) TermsAgreementItem( title = "(필수) 만 14세 이상입니다.", - onCheckedChange = { onToggleOverFourteen() }, + onCheckedChange = onToggleOverFourteen, isChecked = uiState.agreedOverFourteen, )presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginScreen.kt (1)
40-60: LoginScreenContainer의 ViewModel 주입과 사이드 이펙트 처리가 깔끔합니다.
viewModel: LoginViewModel = hiltViewModel()로 기본값을 둔 덕분에 NavGraph 쪽에서는 필요한 경우에만 명시적으로 주입을 덮어쓸 수 있고, 프리뷰/테스트 시에도 별도 오버로드 없이 대체 ViewModel을 주입하기 수월해졌습니다.collectSideEffect에서when (sideEffect)분기를is LoginSideEffect.NavigateToHome -> navigateToHome()형태로 정리한 것도 가독성이 좋아졌고, sealed interface 기반이라 컴파일 타임에 분기 누락도 잡을 수 있겠습니다.- 프리뷰는 실제 UI만 렌더링하도록
LoginScreen만 호출하고 있어 Hilt 의존성 없이도 안전하게 동작할 것 같아요.@Preview(showBackground = true)변경도 디자인 확인에 도움이 될 듯합니다.현재 구조에서는 상태를 사용하지 않고 사이드 이펙트만 처리하고 있는데, 향후 로딩 표시나 버튼 비활성화 등이 필요해지면
container.stateFlow/collectAsState로 확장만 해주면 될 것 같습니다.Also applies to: 133-140
presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginViewModel.kt (1)
20-54: kakaoLogin 에지 케이스 및 실패 시 UX 보완 여지가 있습니다.
- token·error 둘 다 null인 경우 로딩 해제 누락 가능성
현재 분기:
intent { reduce { state.copy(isLoading = true) } when { token != null -> processKakaoLoginSuccess(token) error != null -> { reduce { state.copy(isLoading = false) } Log.e("KakaoLogin", "카카오 로그인 실패", error) } } }Kakao SDK가 보장해 준다면 문제가 없지만, 방어적으로 보면
token == null && error == null인 경우isLoading이 계속 true로 남습니다. 에지 케이스 방지를 위해 else 분기를 추가하는 것을 권장합니다:- when { - token != null -> processKakaoLoginSuccess(token) - - error != null -> { - reduce { state.copy(isLoading = false) } - Log.e("KakaoLogin", "카카오 로그인 실패", error) - } - } + when { + token != null -> processKakaoLoginSuccess(token) + + error != null -> { + reduce { state.copy(isLoading = false) } + Log.e("KakaoLogin", "카카오 로그인 실패", error) + } + + else -> { + reduce { state.copy(isLoading = false) } + Log.e("KakaoLogin", "토큰과 에러가 모두 null 입니다.") + } + }
- 로그인 실패 시 UI 피드백
processKakaoLoginSuccess와error != null분기 모두 Log만 남기고 있어서, 사용자 입장에서는 “아무 일도 안 일어나는 것처럼” 보일 수도 있습니다. 나중에 여유가 되면LoginSideEffect.ShowLoginError(message)같은 사이드 이펙트를 추가해서 스낵바/다이얼로그로 노출하는 것도 고려해 볼 만합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (56)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviSideEffect.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviState.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt(2 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideScreen.kt(3 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginScreen.kt(2 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt(2 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt(7 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt(2 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt(3 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt(4 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt(3 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/setting/model/mvi/SettingSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt(3 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashSideEffect.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/terms/TermsAgreementScreen.kt(4 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementViewModel.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementState.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt(6 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt(0 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineSideEffect.kt(1 hunks)presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt(2 hunks)presentation/src/test/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModelTest.kt(0 hunks)
💤 Files with no reviewable changes (15)
- presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviSideEffect.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviState.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt
- presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineIntent.kt
- presentation/src/test/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModelTest.kt
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-13T09:06:19.028Z
Learnt from: wjdrjs00
Repo: YAPP-Github/Bitnagil-Android PR: 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/login/model/LoginState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginSideEffect.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashSideEffect.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/setting/model/mvi/SettingSideEffect.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt
📚 Learning: 2025-07-23T13:31:46.809Z
Learnt from: l5x5l
Repo: YAPP-Github/Bitnagil-Android PR: 41
File: presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageState.kt:6-14
Timestamp: 2025-07-23T13:31:46.809Z
Learning: In the Bitnagil Android project, MviState interface extends Parcelable, so any class implementing MviState automatically implements Parcelable. Therefore, Parcelize annotation works correctly without explicitly adding Parcelable implementation.
Applied to files:
presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListState.kt
📚 Learning: 2025-07-23T13:32:26.263Z
Learnt from: l5x5l
Repo: YAPP-Github/Bitnagil-Android PR: 41
File: presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageIntent.kt:6-6
Timestamp: 2025-07-23T13:32:26.263Z
Learning: In the Bitnagil Android project's MVI architecture, Intent classes like `LoadMyPageSuccess` are named to represent successful API response results that carry loaded data, not just user actions. This naming convention is used for future API integration where the intent will be triggered when my page data loading succeeds from the server.
Applied to files:
presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageState.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt
📚 Learning: 2025-11-22T08:34:05.454Z
Learnt from: l5x5l
Repo: YAPP-Github/Bitnagil-Android PR: 151
File: presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryScreen.kt:122-153
Timestamp: 2025-11-22T08:34:05.454Z
Learning: In Jetpack Compose LazyColumn, calling `stickyHeader` and `itemsIndexed` inside a `forEach` loop on a collection works correctly. The LazyListScope DSL properly handles these calls even when they are nested inside a forEach block, allowing sticky headers to function as expected.
Applied to files:
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt
📚 Learning: 2025-07-21T10:38:49.104Z
Learnt from: l5x5l
Repo: YAPP-Github/Bitnagil-Android PR: 38
File: presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/textbutton/TextButton.kt:30-35
Timestamp: 2025-07-21T10:38:49.104Z
Learning: presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/textbutton/TextButton.kt의 TextButton 컴포넌트는 임시로 구현된 컴포넌트로, 디자인 시스템 구현시 대체 예정입니다.
Applied to files:
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt
🧬 Code graph analysis (10)
presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt (2)
fetchRoutines(14-40)fetchRoutines(15-19)presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (1)
fetchWeeklyRoutinesUseCase(35-312)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt (2)
navigateToEmotion(112-116)navigateToRegisterRoutine(118-122)presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)
RecommendLevelBottomSheet(24-75)
presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementScreen.kt (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementViewModel.kt (3)
navigateToPrivacyPolicy(83-87)navigateToTermsOfService(77-81)navigateToBack(89-93)presentation/src/main/java/com/threegap/bitnagil/presentation/terms/component/TermsAgreementItem.kt (1)
TermsAgreementItem(26-76)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (1)
container(13-39)
🪛 detekt (1.23.8)
presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt
[warning] 49-49: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ 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 (49)
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideSideEffect.kt (1)
3-5: LGTM!MviSideEffect 상속 제거가 올바르게 되었습니다. Orbit에서는 별도의 기본 side effect 인터페이스가 필요하지 않습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/model/GuideState.kt (1)
3-13: LGTM!Orbit 마이그레이션에 맞게 MviState 상속 및 @parcelize 제거가 올바르게 되었습니다. companion object의 INIT 패턴은 container 초기화 시 명확한 초기 상태를 제공합니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideScreen.kt (2)
29-35: LGTM!Orbit Compose의
collectAsState()와collectSideEffect를 올바르게 사용하고 있습니다. Intent 기반 상호작용에서 직접 메서드 참조로 전환하여 코드가 더 간결해졌습니다.
46-49: 직접 메서드 참조 사용이 깔끔합니다.
viewModel::onShowGuideBottomSheet,viewModel::navigateToBack같은 메서드 참조 패턴이 Intent 객체 생성 대비 보일러플레이트를 줄여줍니다.presentation/src/main/java/com/threegap/bitnagil/presentation/guide/GuideViewModel.kt (2)
14-16: LGTM!
ContainerHost<GuideState, GuideSideEffect>구현과container(initialState = GuideState.INIT)초기화가 Orbit 패턴에 맞게 올바르게 작성되었습니다.
18-38: Orbit DSL 사용이 적절합니다.
intent { reduce { } }패턴으로 상태 변경,intent { postSideEffect() }패턴으로 사이드 이펙트 발행이 Orbit의 표준 패턴을 잘 따르고 있습니다. 기존 MviViewModel의 복잡한 Intent 처리 로직 대비 훨씬 간결해졌습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashSideEffect.kt (1)
3-8: LGTM!MviSideEffect 상속 제거 및 standalone sealed interface로의 전환이 Orbit 패턴에 맞게 잘 구현되었습니다.
NavigateToOnboarding추가도 적절합니다.presentation/src/main/java/com/threegap/bitnagil/presentation/splash/model/SplashState.kt (1)
5-19: LGTM!MviState 상속 제거 및
INITcompanion object 패턴 적용이 다른 ViewModel들(GuideViewModel 등)과 일관성 있게 잘 구현되었습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashScreen.kt (1)
33-52: LGTM!Orbit의
collectAsState()와collectSideEffect사용이 올바르게 구현되었습니다. 모든 SplashSideEffect 케이스가 적절히 처리되고 있습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/splash/SplashViewModel.kt (2)
19-25: LGTM!ContainerHost 구현과 container 초기화가 GuideViewModel과 일관성 있게 잘 구현되었습니다.
55-73: 재귀 호출 패턴 검토 필요.
intent블록 내부에서viewModelScope.launch로 재귀 호출하는 패턴이 사용되고 있습니다. 동작은 하겠지만, Orbit의 intent 컨텍스트 밖에서 다시onAnimationCompleted()를 호출하는 구조입니다.대안으로
repeatOnLifecycle이나Flow를 사용한 상태 관찰 패턴을 고려해볼 수 있지만, 현재 구현도 기능적으로 문제없이 동작합니다.현재 구현이 의도한 대로 동작하는지 확인해주세요. auto login이 완료되지 않은 상태에서 애니메이션이 완료되면 100ms 간격으로 폴링하여 로그인 완료를 기다리는 것이 맞는지 확인 부탁드립니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/setting/model/mvi/SettingSideEffect.kt (1)
3-6: MviSideEffect base class removal is correctly implemented.The migration of
SettingSideEffectfrom extendingMviSideEffectto a standalone sealed class is clean and compatible with Orbit MVI. Verification confirms:
SettingViewModelcorrectly implementsContainerHost<SettingState, SettingSideEffect>and usespostSideEffect()to emit bothNavigateToLoginandNavigateToWithdrawalSettingScreenproperly collects side effects using Orbit'scollectSideEffectand handles all cases in the when statement- No breaking changes—Orbit's
ContainerHosthandles the sealed class type parameters directly without requiring a custom base classpresentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageState.kt (1)
3-10: LGTM!MviState 상속 제거가 깔끔하게 완료되었습니다. Orbit container 초기화를 위한
Initcompanion object가 적절히 유지되어 있습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt (1)
32-43: LGTM!Orbit의
collectAsState()확장함수를 사용한 상태 수집 방식으로 올바르게 마이그레이션되었습니다.ContainerHost에서 직접 호출하는 패턴이 다른 ViewModel들(예: GuideViewModel)과 일관성 있게 적용되었습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/model/MyPageSideEffect.kt (1)
3-3: LGTM!MviSideEffect 상속 제거가 완료되었습니다. 현재 MyPageViewModel에서 side effect를 사용하지 않으므로 빈 sealed class로 유지하는 것이 적절합니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageViewModel.kt (1)
13-17: LGTM!Orbit
ContainerHost패턴으로의 마이그레이션이 올바르게 구현되었습니다.GuideViewModel과 일관된 구조를 따르고 있습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt (1)
7-27: LGTM! Orbit 패턴에 맞는 깔끔한 마이그레이션입니다.
INITcompanion object를 통한 초기 상태 제공 방식이GuideState.INIT등 다른 ViewModel과 일관성 있게 구현되었습니다. MviState/Parcelize 제거 후 순수 data class로 전환한 것이 적절합니다.presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineSideEffect.kt (1)
3-6: LGTM! Sealed interface로 side effect를 깔끔하게 정의했습니다.
data object와data class를 적절히 구분하여 사용했습니다. Orbit의postSideEffect패턴과 잘 맞습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (2)
51-58: LGTM! Orbit Compose 통합이 올바르게 구현되었습니다.
collectAsState()와collectSideEffect를 사용하여 상태와 사이드 이펙트를 적절히 처리하고 있습니다.when분기에서 모든 side effect 케이스를 exhaustive하게 처리하는 점도 좋습니다.
63-74: 직접 메서드 참조 방식으로 깔끔하게 전환되었습니다.
sendIntent패턴에서viewModel::methodName형태의 직접 메서드 참조로 변경하여 코드가 더 간결하고 타입 안전해졌습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineViewModel.kt (2)
21-28: LGTM! Orbit ContainerHost 패턴이 올바르게 적용되었습니다.
GuideViewModel과 동일한 패턴으로ContainerHost<State, SideEffect>를 구현하고container(initialState = ...)로 초기화하는 방식이 일관성 있게 적용되었습니다.
112-122: LGTM! Side effect 발행 패턴이 올바릅니다.
intent { postSideEffect(...) }패턴이GuideViewModel.navigateToBack()과 동일하게 적용되어 일관성 있습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt (1)
3-3: MviState → Parcelable 직접 구현 전환은 방향성/동작 면에서 적절해 보입니다기존에
MviState가Parcelable을 상속하던 구조에서, 이 상태 클래스가 직접Parcelable을 구현하도록 바꾼 것으로 보여서,@Parcelize가 유지되는 한 직렬화 동작은 그대로 유지될 것 같습니다.한 가지 정도만 확인 부탁드립니다:
Date,Time,RepeatType,SelectableDay,WriteRoutineType등 이 상태가 들고 있는 타입들이 모두Parcelable(또는@Parcelize등을 통해 지원되는 타입) 인지, 혹은 기본적으로 Parcel에 쓸 수 있는 타입인지 한 번 더 확인해 주세요. 특히 이 상태를Intent/Bundle로 넘기는 경로가 있다면, 여기에서만Parcelable구현 방식이 바뀐 것이기 때문에 컴파일/런타임 크래시가 없는지만 체크해 두면 좋겠습니다.전반적으로는 MVI 공용 추상화 의존성을 제거하면서도 이전과 같은 직렬화 특성을 유지하는 깔끔한 리팩토링으로 보입니다. 과거 PR 리딩에서
MviState가Parcelable을 상속하고 있던 것으로 기억해서, 이번 변경은 타입 의존성만 줄인 동등 치환(refactor)로 이해했습니다. Based on learnings.Also applies to: 33-33
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt (1)
3-18: LGTM!MviState에서 Parcelable로의 마이그레이션이 올바르게 수행되었습니다.
@Parcelize어노테이션과 함께 Parcelable 구현이 Orbit의 savedStateHandle 기반 상태 저장과 잘 호환됩니다.presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt (1)
3-6: LGTM!MviSideEffect 상속 제거가 Orbit 패턴에 맞게 올바르게 처리되었습니다. Orbit은 side effect 타입에 특별한 인터페이스를 요구하지 않으므로 plain sealed class가 적합합니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt (1)
19-20: LGTM!Orbit compose 확장 함수(
collectAsState,collectSideEffect)로의 마이그레이션이 올바르게 수행되었습니다. 이 패턴은 다른 마이그레이션된 ViewModel들과 일관성을 유지합니다.Also applies to: 27-27, 33-38
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (1)
22-30: LGTM!Orbit ContainerHost 구현이 올바르게 설정되었습니다.
savedStateHandle을 사용한 상태 저장 지원이 프로세스 종료 후 복원에 유용합니다.presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingSideEffect.kt (1)
3-7: LGTM! MVI 기반 클래스 제거 완료MviSideEffect 상속을 제거하여 Orbit으로의 마이그레이션을 깔끔하게 완료했습니다. Public API는 그대로 유지되어 호환성에 문제가 없습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/model/mvi/OnBoardingState.kt (1)
9-12: LGTM! 상태 클래스 마이그레이션 적절MviState 제거와 함께
object를data object로 변경한 것은 최신 Kotlin sealed class 권장사항에 부합하며, Parcelable 구현도 명시적으로 유지되어 있습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingScreen.kt (1)
23-24: LGTM! Orbit Compose 확장 함수 적용 완료Orbit의
collectAsState와collectSideEffect를 사용하도록 깔끔하게 마이그레이션되었습니다. UI 로직은 변경 없이 상태 수집 방식만 Orbit 패턴으로 전환되었습니다.Also applies to: 32-47
presentation/src/main/java/com/threegap/bitnagil/presentation/onboarding/OnBoardingViewModel.kt (3)
40-48: LGTM! Orbit ContainerHost 구현 적절MviViewModel에서 Orbit의 ContainerHost로 올바르게 마이그레이션되었습니다.
savedStateHandle을 활용한 container 초기화와 초기 상태 설정이 적절합니다.
73-86: LGTM! intent/reduce 패턴 일관성 있게 적용모든 상태 변경 로직이 Orbit의
intent/reduce블록을 사용하도록 체계적으로 마이그레이션되었습니다. 특히selectPrevious함수의 복잡한 분기 처리도 적절하게 변환되었습니다.Also applies to: 88-124, 126-146, 148-164, 166-204, 218-272, 306-318, 324-338, 340-360, 362-364
320-322: LGTM! Job 취소 로직 적절이 함수는 단순히 Job을 취소하는 역할만 하므로
intent블록이 불필요합니다. 상태 변경이 없는 적절한 구현입니다.presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt (1)
3-7: LGTM!Orbit MVI로 마이그레이션하면서
MviSideEffect상속을 제거한 것은 올바른 변경입니다. Orbit은 side effect에 대해 별도의 기본 인터페이스를 요구하지 않습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListState.kt (1)
6-29: LGTM - INIT 패턴이 프로젝트 전반의 다른 State 클래스들과 일관성 있게 적용되었습니다.
GuideState.INIT,LoginState.INIT등 다른 State 클래스들과 동일한 패턴으로 초기 상태를 정의했습니다.한 가지 참고 사항:
LocalDate.now()를INIT에서 사용하면 테스트 시 시간 의존성이 생길 수 있습니다. 필요시 테스트에서 명시적으로 날짜를 지정한 State를 생성하여 사용하면 됩니다.presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt (1)
31-46: Orbit ContainerHost 마이그레이션이 잘 구현되었습니다.
ContainerHost인터페이스 구현과container초기화가 올바르게 설정됨SavedStateHandle에서 날짜 파싱 시 안전한 fallback 처리init블록에서 초기 상태 설정과 데이터 로드가 적절하게 수행됨presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt (2)
36-54: Orbit Compose 통합이 올바르게 구현되었습니다.
collectAsState()와collectSideEffect를 사용한 상태 및 사이드 이펙트 수집이 Orbit 패턴에 맞게 적용됨- 모든
RoutineListSideEffect케이스가when블록에서 처리됨- ViewModel 메서드 참조(
viewModel::hideDeleteConfirmBottomSheet등)를 콜백으로 사용하여 코드가 간결해짐
149-159: Preview에서RoutineListState.INIT를 사용한 것이 적절합니다.State의 INIT 인스턴스를 Preview에서 활용하여 일관된 초기 상태를 보여줍니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementSideEffect.kt (1)
3-8: LGTM! Orbit 마이그레이션이 올바르게 적용되었습니다.
MviSideEffect상속을 제거하고 순수 sealed interface로 변경하여 Orbit 패턴에 맞게 잘 리팩토링되었습니다.NavigateToBack추가도 적절합니다.presentation/src/main/java/com/threegap/bitnagil/presentation/terms/model/TermsAgreementState.kt (1)
3-23: LGTM! State 구조가 Orbit 패턴에 맞게 깔끔하게 리팩토링되었습니다.
MviState상속 제거와INITcompanion object 추가가 적절하며, computed properties(isAllAgreed,submitEnabled)의 로직도 그대로 유지되어 기능 동작에 문제가 없습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementViewModel.kt (1)
15-94: LGTM! Orbit 기반 ViewModel으로 깔끔하게 마이그레이션되었습니다.
ContainerHost구현과 explicit action 메서드들이 올바르게 작성되었으며, 다음 사항들이 적절하게 처리되었습니다:
isLoading체크를 통한 동시 수정 방지 (lines 24, 37, 44, 51)submitTermsAgreement의 성공/실패 처리 및 side effect 발행- 모든 navigation helper 메서드의 올바른 구현
GuideViewModel 패턴과 일관성 있게 구현되었습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/terms/TermsAgreementScreen.kt (2)
27-57: LGTM! Orbit 기반 UI 연동이 올바르게 구현되었습니다.
collectAsState와collectSideEffect를 사용한 Orbit 패턴 적용이 적절하며, ViewModel 메서드 참조를 통한 콜백 연결도 깔끔합니다.
140-154: LGTM! Preview가 올바르게 업데이트되었습니다.
TermsAgreementState.INIT를 사용하여 초기 상태를 명시적으로 설정한 것이 좋습니다.presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineSideEffect.kt (1)
3-5: Orbit 전환에 맞는 SideEffect 타입 변경으로 충분합니다기존 MviSideEffect 의존성을 제거하고 순수 sealed class로 둔 구조면 Orbit ContainerHost의 sideEffect 타입으로 사용하기에 충분해 보입니다. 별다른 부작용 없이 깔끔한 정리 같습니다.
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (1)
46-47: Orbit Compose 확장 함수로의 전환이 패턴에 잘 맞습니다
viewModel.collectAsState()와viewModel.collectSideEffect { ... }사용 방식이 ContainerHost 기반 ViewModel 패턴과 일관적이고, sideEffect 콜백에서도 네비게이션/토스트만 처리하고 있어 안정적으로 보입니다.현재 사용하는 Orbit 버전에서
collectAsState/collectSideEffect확장 함수 시그니처가 동일한지만 한 번 빌드해서 확인해 주시면 좋겠습니다.Also applies to: 54-66
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)
159-212: 전반적인 intent/reduce 및 등록/수정 플로우는 잘 마이그레이션되었습니다
- 이름/서브루틴/요일/기간/시간/각종 UI 플래그 변경을 모두
intent { reduce { state.copy(...) } }패턴으로 일관되게 옮겨주신 부분은 읽기 쉽고 Orbit 관점에서도 자연스럽습니다.registerRoutine쪽에서
currentState.loading가드로 중복 클릭 방지,repeatType에 따른repeatDay계산,noRepeatRoutine시Date.now()사용,- Add/Edit 분기와
RoutineUpdateType.Today/Tomorrow매핑,- 성공 시 네비게이션 + 토스트, 실패 시
loading = false복구
등이 모두 기존 의도를 잘 유지하고 있습니다.- 특히 성공 케이스에서
loading을 되돌리지 않고 바로MoveToPreviousScreensideEffect만 보내는 부분은, 이전에 공유해 주셨던 “성공 시 네비게이션으로 ViewModel 라이프사이클이 종료되므로 로딩 리셋이 굳이 필요 없다”는 패턴과 동일해서 괜찮아 보입니다. Based on learnings.위에서 코멘트 드린
loading실패 분기와selectAllTime만 보완되면 나머지 로직은 그대로 가져가셔도 좋을 것 같습니다.Also applies to: 222-280, 281-334, 335-430
presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginSideEffect.kt (1)
3-6: LoginSideEffect를 MVI 공용 베이스에서 분리한 방향 좋습니다.Orbit ContainerHost와 느슨하게 결합된 순수 sealed interface + object 구조라, 네비게이션 사이드 이펙트만 명확하게 드러나고 있습니다. 현재 요구사항 기준으로 추가 액션은 없어 보여요.
presentation/src/main/java/com/threegap/bitnagil/presentation/login/model/LoginState.kt (1)
3-13: INIT 정적 상태 도입으로 초기 상태 관리가 명확해졌습니다.생성자 기본값 대신
LoginState.INIT를 통해 초기 상태를 한 곳에서만 정의하도록 한 구조가 Orbit 컨테이너 초기화 패턴과 잘 맞습니다. 추가적인 null/디폴트 케이스도 없어져서 추적하기 수월할 것 같아요.presentation/src/main/java/com/threegap/bitnagil/presentation/login/LoginViewModel.kt (1)
3-13: No action needed. The Orbit DSL functions (intent,subIntent,reduce,postSideEffect) are available without explicit imports fromorg.orbitmvi.orbit.syntax.simple.*. GuideViewModel and other ViewModels in the project already use these functions successfully without importing them. The code compiles and runs without errors, confirming they are provided by the project's Orbit bundle configuration.Likely an incorrect or invalid review comment.
[ PR Content ]
각 화면의 ViewModel을 구현할 때 기존 common/viewModel에 선언한 MviViewModel을 사용하는 방식 대신
각 ViewModel에서 직접적으로 orbit을 사용하는 방식으로 수정합니다.
Related issue
Screenshot 📸
x
Work Description
To Reviewers 📢
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.