Skip to content

[REFACTOR] BaseViewModel 걷어내고 Orbit MVI 자체 기능 사용하기#232

Merged
DongChyeon merged 16 commits into
developfrom
refactor/#231-remove-baseviewmodel
Jul 14, 2025
Merged

[REFACTOR] BaseViewModel 걷어내고 Orbit MVI 자체 기능 사용하기#232
DongChyeon merged 16 commits into
developfrom
refactor/#231-remove-baseviewmodel

Conversation

@DongChyeon
Copy link
Copy Markdown
Member

@DongChyeon DongChyeon commented Jul 12, 2025

Related issue 🛠

closed #231

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

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

CheckPoint ✅

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

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

Work Description ✏️

사용하던 BaseViewModel이 오히려 가독성 및 예상치 못한 동작을 발생시키므로 제거하였습니다.

  • 기존 ViewModel에서 공통적으로 사용하던 BaseViewModel을 제거하고 ContainerHost 기반의 구조로 통일
  • ViewModelScope + intent 혼용되던 구조를 intent 내부로 통합하여 일관성 있는 상태 처리 방식 적용
  • processAction() 내부 로직을 각각의 명확한 책임을 가진 함수로 분리하여 SRP 원칙에 맞게 개선
  • sideEffect 수집 시 Orbit MVI 제공 함수 사용

Uncompleted Tasks 😅

  • N/A

To Reviewers 📢

이슈 읽어보심 좋아요

Summary by CodeRabbit

  • 리팩터

    • 전체 화면 및 뷰모델에서 상태 및 사이드 이펙트 관리를 Orbit MVI 패턴으로 일괄 전환하여, 상태 업데이트와 사이드 이펙트 처리가 더욱 일관되고 선언적으로 변경되었습니다.
    • 기존 BaseViewModel 및 수동 코루틴 관리 방식이 제거되고, intent, reduce, postSideEffect 등 Orbit 컨테이너 기반 구조로 통합되었습니다.
    • 액션 처리 메서드가 processAction으로 통일되었으며, onAction 호출부가 모두 processAction으로 변경되었습니다.
    • 사이드 이펙트 수집이 collectSideEffect를 활용하는 방식으로 단순화되었습니다.
    • 여러 화면에서 사이드 이펙트 처리 로직이 별도의 함수로 분리되어 가독성과 유지보수성이 향상되었습니다.
    • Alarm, Fortune, Home, Mission, Onboarding, Setting, Splash 등 주요 기능의 뷰모델에서 Orbit MVI 패턴 적용 및 구조 개선이 이루어졌습니다.
    • AlarmSnoozeTimerRoute 등 일부 컴포저블에서 navigator 파라미터가 제거되어 호출이 간소화되었습니다.
  • 스타일/코드 정리

    • 중복되는 코드와 수동 플로우 수집, LaunchedEffect 사용 등이 제거되어 코드가 간결해졌습니다.
    • 불필요한 import 제거 및 CoroutineScope 활용으로 코드 정리가 이루어졌습니다.
  • 버그 수정

    • 상태 및 사이드 이펙트 처리의 일관성 강화로 인한 예외 상황 및 동기화 문제 개선 효과가 기대됩니다.
  • 기능 개선

    • Mission 화면에 NavigateToHome 사이드 이펙트가 추가되어 홈 화면으로의 네비게이션이 명확히 처리됩니다.

@DongChyeon DongChyeon requested a review from MoonsuKang July 12, 2025 04:10
@DongChyeon DongChyeon self-assigned this Jul 12, 2025
@DongChyeon DongChyeon added the ♻️ REFACTOR 코드 리팩토링(전면 수정) label Jul 12, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 12, 2025

"""

Walkthrough

이 변경사항은 프로젝트 전반에 걸쳐 BaseViewModel 추상 클래스를 완전히 제거하고, 모든 ViewModel이 Orbit MVI의 ContainerHost와 관련 DSL(intent, reduce, postSideEffect 등)을 직접 사용하도록 리팩토링한 것입니다. 기존의 상태 및 부수효과 관리 로직은 Orbit MVI 패턴에 맞게 일관성 있게 재구현되었습니다.

Changes

파일/경로 그룹 변경 요약
core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt BaseViewModel 추상 클래스 및 모든 관련 멤버 삭제
feature/*/ViewModel.kt
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt
feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt
feature/home/src/main/java/com/yapp/home/HomeViewModel.kt
feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt
feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt
feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt
모든 ViewModel이 BaseViewModel 상속을 중단하고, ContainerHost 구현 및 Orbit MVI의 container, intent, reduce, postSideEffect를 직접 사용하도록 리팩터링. 기존 상태/부수효과 관리 메서드 삭제 및 Orbit DSL로 대체. 액션 처리 메서드(processAction 등) 추가.
feature/*/Screen.kt, feature/*/NavGraph.kt ViewModel의 액션 처리 메서드 호출부를 onAction에서 processAction 등으로 변경. 부수효과(사이드이펙트) 수집 로직을 Orbit의 collectSideEffect로 대체. 관련 LaunchedEffect, collectLatest 등 삭제 및 핸들러 함수 추출.
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt
AlarmSnoozeTimerRoute에서 navigator 파라미터 제거 및 관련 코드 정리.
feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt LaunchedEffect를 통한 액션 트리거 코드 제거.
feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt,
feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt,
feature/setting/src/main/java/com/yapp/setting/EditBirthdayScreen.kt,
feature/setting/src/main/java/com/yapp/setting/EditProfileScreen.kt
ViewModel 액션 처리 메서드 명칭을 onAction에서 processAction으로 일괄 변경.

Sequence Diagram(s)

sequenceDiagram
    participant UI
    participant ViewModel
    participant OrbitContainer

    UI->>ViewModel: processAction(action)
    ViewModel->>OrbitContainer: intent { ... }
    OrbitContainer->>ViewModel: state (via reduce)
    ViewModel->>OrbitContainer: postSideEffect(effect)
    OrbitContainer-->>UI: sideEffect emitted
Loading

Assessment against linked issues

Objective Addressed Explanation
BaseViewModel 걷어내기 (#231)
ViewModel을 ContainerHost를 사용하도록 (#231)
updateState, emitSideEffect, collectFlow, launchWithErrorHandler 제거 후 직접 intent/reduce/postSideEffect 사용 (#231)

Suggested labels

✨ FEAT

Suggested reviewers

  • MoonsuKang
    """

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92e1dec and 04fb29d.

📒 Files selected for processing (7)
  • data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt (1 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionContract.kt (1 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (2 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (6 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (7 hunks)
  • feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt (2 hunks)
  • feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • data/src/main/java/com/yapp/data/repositoryimpl/FortuneRepositoryImpl.kt
🚧 Files skipped from review as they are similar to previous changes (4)
  • feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt
  • feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt
🔇 Additional comments (8)
feature/mission/src/main/java/com/yapp/mission/MissionContract.kt (2)

32-32: 새로운 NavigateToHome 사이드 이펙트 추가 승인

기존 패턴과 일관성 있게 구현되었으며, Orbit MVI 리팩토링 목적에 부합합니다.


32-32: 새로운 NavigateToHome 사이드 이펙트 추가가 적절합니다.

기존 네비게이션 사이드 이펙트들과 일관된 패턴을 따르고 있으며, 명명 규칙도 명확합니다.

feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (6)

10-10: Orbit MVI collectSideEffect 임포트 추가 승인

수동 사이드 이펙트 수집에서 Orbit MVI의 내장 기능으로 전환하는 올바른 접근법입니다.


24-29: 사이드 이펙트 처리 패턴 개선 승인

LaunchedEffect와 수동 플로우 수집을 collectSideEffect로 대체한 것은 더 깔끔하고 관용적인 Orbit MVI 패턴입니다. 코드가 더 간결해지고 읽기 쉬워졌습니다.


32-59: handleSideEffect 함수 분리를 통한 관심사 분리 개선

사이드 이펙트 처리 로직을 별도 함수로 분리한 것은 다음과 같은 장점이 있습니다:

  • 단일 책임 원칙(SRP) 준수
  • 코드 가독성 향상
  • 유지보수성 개선

모든 네비게이션 패턴이 일관성 있게 구현되었으며, NavigateToFortuneNavigateToHome에서 동일한 popUpTo 동작을 사용하는 것이 적절합니다.


10-10: Orbit MVI의 collectSideEffect import가 적절합니다.

수동 사이드 이펙트 수집 방식에서 Orbit MVI의 표준 방식으로 전환하는 것이 올바른 접근입니다.


24-26: collectSideEffect 사용으로 개선된 사이드 이펙트 처리

LaunchedEffect와 수동 flow 수집 대신 Orbit MVI의 collectSideEffect 확장 함수를 사용하여 더 간결하고 관용적인 코드가 되었습니다.


32-59: handleSideEffect 함수로 로직 분리가 우수합니다.

사이드 이펙트 처리 로직을 별도 함수로 추출하여 가독성과 유지보수성이 향상되었습니다. 각 네비게이션 케이스의 처리도 적절합니다:

  • NavigateToFortune과 NavigateToHome에서 popUpTo(MissionRoute) { inclusive = true }로 미션 화면을 스택에서 제거하는 것이 올바름
  • NavigateBack의 단순한 뒤로가기 처리도 적절함
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

Documentation and Community

  • 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
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (1)
feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt (1)

236-255: suspend 함수를 intent 블록 내에서 올바르게 호출

checkAndCreateAlarm은 suspend 함수이지만 intent로 래핑되지 않았습니다. saveAlarm의 intent 블록에서 호출될 때 컴파일 오류가 발생합니다.

함수 시그니처를 수정하거나 호출 부분을 수정하세요:

옵션 1: 함수를 일반 함수로 변경 (권장)

-private suspend fun checkAndCreateAlarm(newAlarm: Alarm) {
+private fun checkAndCreateAlarm(newAlarm: Alarm) = intent {

옵션 2: 호출 시 intent 제거

 private fun saveAlarm() = intent {
     val newAlarm = state.toAlarm()
 
     when (state.mode) {
         AlarmAddEditContract.EditMode.EDIT -> updateExistingAlarm(newAlarm)
-        AlarmAddEditContract.EditMode.ADD -> checkAndCreateAlarm(newAlarm)
+        AlarmAddEditContract.EditMode.ADD -> {
+            // intent 블록을 벗어나서 suspend 함수 호출
+            container.orbit.runBlockingIntent {
+                checkAndCreateAlarm(newAlarm)
+            }
+        }
     }
 }
🧹 Nitpick comments (4)
feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt (1)

41-66: 함수명 일관성 개선 제안

Side effect 처리 로직이 잘 구조화되었습니다. 다른 파일들과의 일관성을 위해 함수명을 handleSideEffect (단수)로 통일하는 것을 고려해보세요.

-private fun handleSideEffects(
+private fun handleSideEffect(
     sideEffect: SplashContract.SideEffect,
     navigator: OrbitNavigator,
 ) {
feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt (1)

37-51: first 사용이 스플래시 로직에 적합합니다!

collect 대신 first를 사용하여 첫 번째 값만 처리하는 것이 스플래시 화면의 일회성 체크에 더 적합합니다. 다만, first 블록의 마지막에 true를 반환하는 대신 조건 없이 first()를 사용하는 것이 더 명확할 수 있습니다.

-        }.first { (userId, onboardingCompleted) ->
+        }.first().let { (userId, onboardingCompleted) ->
             if (userId != null && onboardingCompleted) {
                 postSideEffect(SplashContract.SideEffect.NavigateToHome)
             } else {
                 postSideEffect(SplashContract.SideEffect.NavigateToOnboarding)
             }
-            true
         }
feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt (2)

43-48: Container 초기화 패턴 개선 고려

container 초기화 시 즉시 intent를 실행하는 것은 동작하지만, 초기화 로직을 분리하는 것이 더 명확할 수 있습니다.

-override val container: Container<AlarmAddEditContract.State, AlarmAddEditContract.SideEffect> = container(initialState = AlarmAddEditContract.State()) {
-    intent {
-        reduce { state.copy(mode = if (alarmId == -1L) AlarmAddEditContract.EditMode.ADD else AlarmAddEditContract.EditMode.EDIT) }
-        initializeAlarmScreen()
-    }
-}
+override val container: Container<AlarmAddEditContract.State, AlarmAddEditContract.SideEffect> = container(
+    initialState = AlarmAddEditContract.State(
+        mode = if (alarmId == -1L) AlarmAddEditContract.EditMode.ADD else AlarmAddEditContract.EditMode.EDIT
+    )
+)
+
+init {
+    intent {
+        initializeAlarmScreen()
+    }
+}

423-431: 중첩된 intent 사용 개선

postSideEffect의 콜백 내에서 새로운 intent를 생성하는 것보다 별도의 액션을 통해 처리하는 것이 더 명확합니다.

액션을 추가하여 처리하세요:

+// AlarmAddEditContract.Action에 추가
+object UndoHolidaySkip : Action

그리고 다음과 같이 수정하세요:

 onAction = {
-    intent {
-        reduce {
-            state.copy(
-                holidayState = state.holidayState.copy(
-                    isDisableHolidayChecked = false,
-                ),
-            )
-        }
-    }
+    processAction(AlarmAddEditContract.Action.UndoHolidaySkip)
 }

processAction에 케이스 추가:

is AlarmAddEditContract.Action.UndoHolidaySkip -> undoHolidaySkip()

새 함수 추가:

private fun undoHolidaySkip() = intent {
    reduce {
        state.copy(
            holidayState = state.holidayState.copy(
                isDisableHolidayChecked = false,
            ),
        )
    }
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 074b725 and 92e1dec.

📒 Files selected for processing (25)
  • core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt (0 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt (1 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (3 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt (2 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt (0 hunks)
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (2 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt (1 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt (2 hunks)
  • feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (4 hunks)
  • feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt (3 hunks)
  • feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditViewModel.kt (14 hunks)
  • feature/home/src/main/java/com/yapp/home/HomeScreen.kt (2 hunks)
  • feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (8 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (2 hunks)
  • feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (6 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt (1 hunks)
  • feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (7 hunks)
  • feature/setting/src/main/java/com/yapp/setting/EditBirthdayScreen.kt (2 hunks)
  • feature/setting/src/main/java/com/yapp/setting/EditProfileScreen.kt (1 hunks)
  • feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt (5 hunks)
  • feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt (4 hunks)
  • feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt (0 hunks)
  • feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt (1 hunks)
  • feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt (1 hunks)
  • feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt (1 hunks)
💤 Files with no reviewable changes (3)
  • feature/setting/src/main/java/com/yapp/setting/SettingScreen.kt
  • feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt
  • core/ui/src/main/java/com/yapp/ui/base/BaseViewModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (8)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt (1)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerScreen.kt (1)
  • AlarmSnoozeTimerRoute (49-59)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (2)
feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt (1)
  • handleSideEffect (94-139)
feature/home/src/main/java/com/yapp/home/HomeScreen.kt (1)
  • handleSideEffect (133-173)
feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (4)
feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt (1)
  • handleSideEffect (93-122)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt (1)
  • handleSideEffect (92-121)
feature/fortune/src/main/java/com/yapp/fortune/FortuneNavGraph.kt (1)
  • handleSideEffect (55-94)
feature/mission/src/main/java/com/yapp/mission/MissionScreen.kt (1)
  • MissionRoute (51-77)
feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt (5)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (1)
  • handleSideEffect (58-67)
feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt (1)
  • handleSideEffect (93-122)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt (1)
  • handleSideEffect (92-121)
feature/home/src/main/java/com/yapp/home/HomeScreen.kt (1)
  • handleSideEffect (133-173)
core/ui/src/main/java/com/yapp/ui/component/snackbar/OrbitSnackBar.kt (1)
  • showCustomSnackBar (119-159)
feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt (2)
feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt (3)
  • refreshUserInfo (189-194)
  • navigateBack (173-175)
  • fetchUserInfo (112-141)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (1)
  • openWebView (247-249)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt (1)
feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (3)
  • fetchIsFirstMission (47-55)
  • startClock (57-87)
  • dismiss (89-91)
feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt (2)
feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt (3)
  • navigateBack (60-62)
  • refreshUserInfo (72-77)
  • fetchUserInfo (42-58)
feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt (1)
  • submitUserInfo (67-97)
feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2)
feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (1)
  • navigateToHome (106-108)
core/common/src/main/java/com/yapp/common/navigation/OrbitNavigator.kt (1)
  • navigateToHome (48-50)
⏰ 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 (37)
feature/fortune/src/main/java/com/yapp/fortune/FortuneRewardScreen.kt (1)

58-59: Orbit MVI 리팩토링이 올바르게 적용되었습니다.

onAction에서 processAction으로의 메소드 호출 변경이 일관되게 적용되어 BaseViewModel에서 Orbit MVI ContainerHost로의 전환이 정확히 이루어졌습니다.

Also applies to: 66-66

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/AlarmInteractionNavGraph.kt (1)

74-74: 네비게이션 파라미터 제거가 올바르게 적용되었습니다.

AlarmSnoozeTimerRoute의 시그니처 변경에 맞춰 navigator 파라미터가 제거되어 Orbit MVI 리팩토링과 일치합니다.

feature/fortune/src/main/java/com/yapp/fortune/FortuneScreen.kt (1)

108-108: 액션 처리 메소드 변경이 일관되게 적용되었습니다.

모든 onAction 호출이 processAction으로 올바르게 변경되어 Orbit MVI 패턴에 맞게 리팩토링되었습니다.

Also applies to: 115-116, 126-126

feature/setting/src/main/java/com/yapp/setting/EditBirthdayScreen.kt (1)

35-51: 모든 액션 디스패치가 Orbit MVI 패턴으로 올바르게 업데이트되었습니다.

EditBirthdayRoute에서 모든 onAction 호출이 processAction으로 일관되게 변경되어 BaseViewModel에서 Orbit MVI ContainerHost로의 전환이 완료되었습니다.

feature/setting/src/main/java/com/yapp/setting/EditProfileScreen.kt (1)

58-87: 복잡한 프로필 편집 화면에서도 Orbit MVI 리팩토링이 완벽하게 적용되었습니다.

EditProfileRoute의 모든 액션 디스패치가 onAction에서 processAction으로 일관되게 변경되어, 이름 업데이트, 성별 토글, 생년월일 관련 액션 등 모든 사용자 상호작용이 새로운 Orbit MVI 패턴을 따르도록 업데이트되었습니다.

feature/setting/src/main/java/com/yapp/setting/SettingNavGraph.kt (1)

16-16: LGTM! Orbit MVI의 관용적 패턴으로 개선

collectSideEffect를 사용하여 수동 side effect 수집을 대체한 것이 좋습니다. 코드가 더 간결해지고 Orbit MVI의 관용적 패턴을 따릅니다.

Also applies to: 27-29, 37-39, 84-86

feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingNavGraph.kt (1)

12-12: 일관된 Orbit MVI 패턴 적용

모든 composable에서 collectSideEffect를 일관되게 사용하여 side effect 처리를 개선했습니다.

참고: handleSideEffect 함수가 viewModel을 매개변수로 받아 NavigateBack 시 상태를 리셋하는 것은 유효한 설계 결정으로 보입니다.

Also applies to: 20-22, 28-30, 36-38, 44-46, 52-54, 60-62, 68-70, 76-78, 84-86

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionScreen.kt (2)

38-38: Side effect 처리 개선

collectSideEffect를 사용하여 manual flow collection을 대체한 것이 좋습니다. 코드가 더 간결하고 Orbit MVI 패턴에 맞습니다.

Also applies to: 48-50


58-67: Side effect 핸들러 추출로 모듈성 향상

Side effect 처리 로직을 별도 함수로 추출하여 관심사 분리와 코드 가독성이 개선되었습니다.

feature/mission/src/main/java/com/yapp/mission/MissionNavGraph.kt (2)

10-10: Orbit MVI 관용구 적용

collectSideEffect를 사용하여 side effect 수집을 개선했습니다. 기존의 수동 coroutine 관리보다 더 명확하고 안전합니다.

Also applies to: 24-26


32-49: Navigation side effect 처리 모듈화

Side effect 처리 로직을 별도 함수로 추출하여 코드 구조가 개선되었습니다. Navigation 옵션도 적절히 설정되어 있습니다.

feature/splash/src/main/java/com/yapp/splash/SplashScreen.kt (1)

25-25: Side effect 수집 패턴 개선

collectSideEffect를 사용하여 Orbit MVI의 관용적 패턴을 적용했습니다. 코드가 더 간결하고 명확해졌습니다.

Also applies to: 34-36

feature/home/src/main/java/com/yapp/home/HomeScreen.kt (2)

80-86: Orbit MVI의 collectSideEffect 사용이 적절합니다!

기존의 수동 side effect 수집 방식을 Orbit MVI의 표준 확장 함수로 대체한 것이 좋습니다. 이는 코드베이스 전반의 일관성을 향상시킵니다.

Also applies to: 123-131


133-173: Side effect 처리 로직의 모듈화가 잘 되었습니다!

handleSideEffect 함수로 추출하여 코드의 가독성과 재사용성이 향상되었습니다. 모든 side effect 타입이 적절히 처리되고 있습니다.

feature/home/src/main/java/com/yapp/alarm/addedit/AlarmAddEditScreen.kt (2)

70-72: Orbit MVI 패턴이 일관되게 적용되었습니다!

다른 화면들과 동일한 방식으로 collectSideEffect를 사용하여 side effect를 처리하도록 변경되었습니다.

Also applies to: 84-86


94-139: Side effect 처리가 명확하고 체계적입니다!

각 side effect 타입별로 적절한 네비게이션 처리와 savedStateHandle 설정이 구현되었습니다. SaveAlarm과 다른 케이스들의 네비게이션 방식 차이도 적절합니다.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/snooze/AlarmSnoozeTimerViewModel.kt (3)

29-36: Orbit MVI ContainerHost 구현이 적절합니다!

BaseViewModel을 제거하고 Orbit의 표준 패턴을 직접 사용하도록 변경되었습니다. container 초기화 시 필요한 로직을 intent로 실행하는 방식이 좋습니다.


41-45: 액션 처리 메서드가 잘 구현되었습니다!

processAction 메서드 추가로 UI 이벤트 처리가 명확해졌습니다.


57-87: 타이머 로직의 Orbit intent 구현이 우수합니다!

while 루프와 reduce를 사용한 상태 업데이트가 깔끔하게 구현되었습니다. 타이머 종료 조건도 명확합니다.

feature/splash/src/main/java/com/yapp/splash/SplashViewModel.kt (1)

20-26: ContainerHost 구현이 적절합니다!

BaseViewModel 제거 후 Orbit의 표준 container 패턴을 사용하도록 변경되었습니다.

feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt (2)

22-30: repeatOnSubscription 사용이 적절합니다!

화면이 다시 표시될 때마다 사용자 정보를 새로고침하는 패턴이 좋습니다.


42-77: Orbit intent 패턴이 잘 적용되었습니다!

모든 함수가 intent 블록을 사용하고, reduce로 상태를 업데이트하며, postSideEffect로 side effect를 처리하는 방식이 일관되고 명확합니다.

feature/home/src/main/java/com/yapp/home/HomeViewModel.kt (4)

38-50: Orbit MVI 컨테이너 초기화 패턴이 적절히 적용되었습니다

BaseViewModel에서 ContainerHost로의 전환이 잘 이루어졌으며, 초기화 로직이 repeatOnSubscription 블록 내에 적절히 배치되었습니다.


84-102: intent 블록과 reduce 패턴이 올바르게 적용되었습니다

상태 업데이트와 부수 효과 처리가 Orbit MVI 패턴에 맞게 잘 구현되었습니다.


168-197: 비동기 작업과 상태 관리가 효과적으로 통합되었습니다

알람 상태 토글 로직이 Orbit MVI의 intent 패턴으로 잘 마이그레이션되었으며, 에러 처리도 적절히 유지되고 있습니다.


304-316: Flow 수집과 상태 업데이트가 적절히 구현되었습니다

intent 블록 내에서 Flow를 수집하고 각 emission에 대해 reduce를 사용하는 패턴이 올바르게 적용되었습니다.

feature/fortune/src/main/java/com/yapp/fortune/FortuneViewModel.kt (3)

30-35: 컨테이너 초기화와 자동 데이터 로딩이 잘 구현되었습니다

초기화 시 loadFortune()을 호출하여 필요한 데이터를 자동으로 로드하는 패턴이 적절합니다.


36-51: 액션 처리 구조가 명확하고 일관성 있게 구현되었습니다

processAction 메서드가 각 액션을 개별 intent 함수로 적절히 위임하고 있어 코드의 가독성과 유지보수성이 향상되었습니다.


53-85: 함수 분리가 단일 책임 원칙(SRP)에 따라 잘 이루어졌습니다

loadFortunefetchAndUpdateFortune으로 분리하여 각 함수의 책임이 명확해졌고, 코드의 가독성이 향상되었습니다.

feature/alarm-interaction/src/main/java/com/yapp/alarm/interaction/action/AlarmActionViewModel.kt (3)

33-40: 컨테이너 초기화 순서가 적절합니다

초기화 블록에서 필요한 데이터를 가져오고(fetchIsFirstMission), 상태를 초기화한 후(initializeAlarmState), 시계를 시작하는(startClock) 순서가 논리적입니다.


71-89: 시계 업데이트 로직이 Orbit의 구조화된 동시성을 잘 활용하고 있습니다

intent 블록 내의 무한 루프는 ViewModel의 생명주기와 함께 자동으로 관리되므로 메모리 누수 걱정이 없습니다.


91-105: 스누즈 로직과 상태 관리가 명확하게 구현되었습니다

무제한 스누즈(-1) 케이스를 적절히 처리하고 있으며, 상태 업데이트와 네비게이션 부수 효과가 잘 분리되어 있습니다.

feature/setting/src/main/java/com/yapp/setting/EditProfileViewModel.kt (3)

26-45: 액션 처리가 체계적으로 구성되었습니다

processAction 메서드가 모든 액션을 명확하게 처리하고 있으며, 각 액션이 전용 intent 함수로 위임되어 코드의 모듈성이 향상되었습니다.


47-110: 입력 유효성 검증이 상태 관리와 잘 통합되었습니다

각 필드 업데이트 시 유효성 검증이 자동으로 수행되어 상태의 일관성이 보장됩니다.


177-200: 헬퍼 함수들이 단일 책임 원칙에 따라 잘 구성되었습니다

resetState, showDialog, hideDialog 등의 헬퍼 함수들이 명확한 책임을 가지고 있어 코드의 가독성과 유지보수성이 향상되었습니다.

feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt (2)

40-47: 컨테이너 초기화가 적절히 구현되었습니다

알람 해제 인텐트 전송과 미션 타입 로드가 초기화 시점에 올바르게 처리되고 있습니다.


77-117: 미션 처리 로직이 명확하고 체계적입니다

미션 타입별 처리가 잘 분리되어 있고, 상태 업데이트와 애니메이션 타이밍이 적절히 관리되고 있습니다.

Comment thread feature/setting/src/main/java/com/yapp/setting/SettingViewModel.kt Outdated
Comment thread feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt Outdated
Comment thread feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt
Comment thread feature/onboarding/src/main/java/com/yapp/onboarding/OnboardingViewModel.kt Outdated
@github-actions
Copy link
Copy Markdown

🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.

➡️ 클릭하여 커버리지 리포트 다운로드

@github-actions
Copy link
Copy Markdown

🚀 테스트 완료 및 커버리지 리포트가 생성되었습니다.

➡️ 클릭하여 커버리지 리포트 다운로드

Copy link
Copy Markdown
Member

@MoonsuKang MoonsuKang left a comment

Choose a reason for hiding this comment

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

읽는데만 한 시간 걸렸네요 ㅋㅋㅋ
외부 프레임워크를 사용할꺼면 BaseViewModel를 안쓰는게 좋은거 같다고 생각은 합니다...

viewModelScope.launch -> intent {} 로 바뀐 부분에 대해 제가 이해한 게 맞는지 검증좀

  • 내부적으로 viewModelScope.launch를 사용하긴 하지만,
  • Orbit의 intentQueue에 등록되어 sequential하게 실행되고,
  • reduce, state 접근 시 항상 최신 상태를 보장해줌
    -> 결과적으로 동시성 이슈나 상태 충돌 없이 안전하게 처리 가능하다?

@DongChyeon
Copy link
Copy Markdown
Member Author

DongChyeon commented Jul 14, 2025

읽는데만 한 시간 걸렸네요 ㅋㅋㅋ 외부 프레임워크를 사용할꺼면 BaseViewModel를 안쓰는게 좋은거 같다고 생각은 합니다...

viewModelScope.launch -> intent {} 로 바뀐 부분에 대해 제가 이해한 게 맞는지 검증좀

  • 내부적으로 viewModelScope.launch를 사용하긴 하지만,
  • Orbit의 intentQueue에 등록되어 sequential하게 실행되고,
  • reduce, state 접근 시 항상 최신 상태를 보장해줌
    -> 결과적으로 동시성 이슈나 상태 충돌 없이 안전하게 처리 가능하다?
val realContainer: Container<STATE, SIDE_EFFECT> =
        viewModelScope.container(state, buildSettings, onCreate)
override val scope: CoroutineScope = parentScope + settings.eventLoopDispatcher
private val intentJob = Job(scope.coroutineContext[Job])
private val dispatchChannel = Channel<Pair<CompletableJob, suspend ContainerContext<STATE, SIDE_EFFECT>.() -> Unit>>(Channel.UNLIMITED)

위 코드에서 알 수 있듯이 viewModel 내에서 Container를 생성하면 parentScope(=viewModelScope)를 주입받습니다.

override fun orbit(orbitIntent: suspend ContainerContext<STATE, SIDE_EFFECT>.() -> Unit): Job {
    initialiseIfNeeded()

    val job = Job(intentJob)
    dispatchChannel.trySend(job to orbitIntent)
    return job
}

@OrbitDsl
internal fun <STATE : Any, SIDE_EFFECT : Any> Container<STATE, SIDE_EFFECT>.intent(
    registerIdling: Boolean = true,
    transformer: suspend ContainerContext<STATE, SIDE_EFFECT>.() -> Unit
): Job = orbit {
    withIdling(registerIdling) {
        transformer()
    }
}

intent 함수는 내부적으로 orbit { } 을 호출하며, 이는 위에서 주입받은 viewModelScope 기반의 CoroutineScope에서 실행됩니다.
따라서 intent는 결국 viewModelScope.launch { } 와 동일한 코루틴 컨텍스트를 사용합니다.

또한, orbit { } 호출 시 intent가 dispatchChannel에 등록되어 순차적으로 실행되기 때문에 intent 내에서 state 접근 시 항상 최신 상태를 가져올 수 있는 것으로 압니다!

@DongChyeon DongChyeon merged commit e0ba516 into develop Jul 14, 2025
2 checks passed
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.

[REFACTOR] BaseViewModel 걷어내고 Orbit MVI 자체 기능 사용하기

2 participants