Skip to content

[Refactor/#207] 루틴 데이터 아키텍처 개선#208

Open
wjdrjs00 wants to merge 13 commits intodevelopfrom
refactor/#207-routine-logic
Open

[Refactor/#207] 루틴 데이터 아키텍처 개선#208
wjdrjs00 wants to merge 13 commits intodevelopfrom
refactor/#207-routine-logic

Conversation

@wjdrjs00
Copy link
Copy Markdown
Member

@wjdrjs00 wjdrjs00 commented Mar 26, 2026

[ PR Content ]

루틴 데이터도 Flow 기반 ssot 방식으로 변경하고, Optimistic Update 책임을 data 계층으로 변경했습니다.

Related issue

Screenshot 📸

  • N/A

Work Description

  • RoutineLocalDataSource 추가로 루틴 데이터의 SSOT(StateFlow) 구축
  • RoutineRepositoryImpl에서 debounce 배치 sync + rollback 처리 -> HomeViewModel의 책임 제거
  • HomeViewModel / RoutineListViewModel 모두 동일한 Repository Flow를 구독해 자동 갱신
  • A→B→A 토글 시 원래 상태와 동일하면 서버 API 미호출 최적화
  • optimistic update 실패 시 다이얼로그 추가
  • @IoDispatcher 주입으로 테스트 가능성 확보 후 단위 테스트 8개 작성

To Reviewers 📢

  • 이번 작업에서 가장 신경 쓴 부분은 각 레이어의 책임을 명확하게 분리해보고자 했습니다..! 이에 따라서 루틴 완료 처리와 optimistic update는 기존 viewmodel에서 전부 관리하던 방식에서 각자 domain과 data에서 수행하도록 개선하여 ui는 상태(루틴)만을 관찰하도록 하여 영향을 받지 않는 방향성으로 개선을 했습니다!
  • 궁금한 사항이 있다면 질문이나 리뷰 남겨주세요~
  • 추가로 네비게이션 작업내용이 딸려왔는데 해당 부분은 무시해주세요.. (분기를 잘못타서,, 이전 pr 내용이 중복되었습니다..)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 루틴 완료 상태 저장 실패 시 사용자에게 알려주는 오류 알림 추가
    • 루틴 데이터 로컬 캐싱으로 오프라인 환경에서도 이전 데이터 접근 가능
  • 버그 수정

    • 네트워크 문제로 루틴 완료 저장 실패 시 UI에 명확한 피드백 제공
  • 개선 사항

    • 루틴 토글 시 즉각적인 UI 업데이트로 더 빠른 반응성 제공
    • 주간 루틴 정보 조회 시 더욱 안정적이고 반응적인 업데이트

@wjdrjs00 wjdrjs00 self-assigned this Mar 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

루틴 데이터 아키텍처를 Flow 기반 SSOT(Single Source of Truth)로 전환하고, 낙관적 업데이트와 배치 동기화 책임을 데이터 계층으로 이전했습니다. HomeRoute를 enum에서 sealed interface로 변경하고, 이벤트 기반 제어 흐름을 관찰 기반으로 리팩터링했습니다.

Changes

Cohort / File(s) Summary
DI 구성
app/src/main/java/com/threegap/bitnagil/di/data/CoroutineModule.kt, data/src/main/java/com/threegap/bitnagil/data/di/CoroutineQualifier.kt, app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
@IoDispatcher 한정자 추가, CoroutineModule 로 IO 디스패처 제공, RoutineLocalDataSourceImpl DI 바인딩 추가
네비게이션 계층
app/src/main/java/com/threegap/bitnagil/navigation/home/HomeRoute.kt, app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavigator.kt, app/src/main/java/com/threegap/bitnagil/navigation/home/HomeBottomNavigationBar.kt, app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
HomeRoute를 enum에서 @Serializable sealed interface로 변경, type-safe 라우팅 추가, HomeNavigator에 navigateTo(route: HomeRoute) 메서드 추가, HomeBottomNavigationBar를 선택 상태와 콜백 기반으로 리팩터링
로컬 데이터 소스
data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt, data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt
RoutineLocalDataSource 인터페이스 및 구현체 추가, StateFlow 기반 일정 캐싱, 완료 상태 관리, 낙관적 토글 적용
루틴 리포지토리
data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
fetchWeeklyRoutines()observeWeeklyRoutines() (Flow 기반), syncRoutineCompletion()applyRoutineToggle() (개별 토글), 배치 flush 및 롤백 로직 추가, syncError SharedFlow 노출, 이벤트 흐름 제거
도메인 계층 - 모델
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/ToggleStrategy.kt, domain/src/main/java/com/threegap/bitnagil/domain/routine/model/WriteRoutineEvent.kt, domain/src/main/java/com/threegap/bitnagil/domain/onboarding/model/OnBoardingRecommendRoutineEvent.kt
ToggleStrategy를 presentation에서 domain으로 이동, WriteRoutineEventOnBoardingRecommendRoutineEvent 제거
도메인 계층 - 리포지토리
domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt, domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt
fetchWeeklyRoutines()observeWeeklyRoutines(), syncRoutineCompletion() 제거, applyRoutineToggle() 추가, syncError SharedFlow 추가, 이벤트 흐름 API 제거
도메인 계층 - 유스케이스
domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveWeeklyRoutinesUseCase.kt, domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ApplyRoutineToggleUseCase.kt, domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveRoutineSyncErrorUseCase.kt, domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/RoutineCompletionUseCase.kt, domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetOnBoardingRecommendRoutineEventFlowUseCase.kt
FetchWeeklyRoutinesUseCaseObserveWeeklyRoutinesUseCase (Flow 기반), ApplyRoutineToggleUseCase 신규 추가, GetWriteRoutineEventFlowUseCaseObserveRoutineSyncErrorUseCase (단순화), 기존 이벤트 기반 유스케이스 제거
프레젠테이션 계층 - ViewModel
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt, presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt
의존성 유스케이스 전환, init 로직을 container 기반 관찰로 변경, 낙관적 업데이트 및 배치 로직 제거, dismissSyncErrorDialog() 메서드 추가, 이벤트 수집 로직 제거
프레젠테이션 계층 - 상태
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt
loadingCountisLoading 제거, showSyncErrorDialog 추가
프레젠테이션 계층 - UI
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilConfirmDialog.kt, presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt, presentation/src/main/java/com/threegap/bitnagil/presentation/screen/withdrawal/component/WithdrawalConfirmDialog.kt
BitnagilConfirmDialog 신규 컴포넌트 추가, HomeScreen에 동기화 오류 대화상자 통합, WithdrawalConfirmDialog를 새 컴포넌트로 위임하여 중복 코드 제거
테스트
data/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.kt
RoutineRepositoryImpl 테스트 스위트 신규 추가: 주간 관찰 캐싱, 낙관적 토글, 배치 동기화, 롤백 동작 검증

Sequence Diagram(s)

sequenceDiagram
    participant ViewModel as HomeViewModel
    participant UseCase as ObserveWeeklyRoutinesUseCase
    participant Repository as RoutineRepository
    participant LocalDS as RoutineLocalDataSource
    participant RemoteDS as RoutineRemoteDataSource

    ViewModel->>UseCase: invoke(startDate, endDate)
    UseCase->>Repository: observeWeeklyRoutines(startDate, endDate)
    Repository->>LocalDS: Check if cached range matches
    alt Cache miss or range mismatch
        Repository->>RemoteDS: Fetch weekly routines
        RemoteDS-->>Repository: Return RoutineSchedule
        Repository->>LocalDS: saveSchedule(schedule, dates)
    end
    Repository-->>UseCase: Emit RoutineSchedule (from LocalDS)
    UseCase-->>ViewModel: Flow<RoutineSchedule>
    ViewModel->>ViewModel: Reduce state with routineSchedule
Loading
sequenceDiagram
    participant ViewModel as HomeViewModel
    participant UseCase as ApplyRoutineToggleUseCase
    participant Repository as RoutineRepository
    participant LocalDS as RoutineLocalDataSource
    participant RemoteDS as RoutineRemoteDataSource

    ViewModel->>UseCase: invoke(dateKey, routineId, isCompleted, states, strategy)
    UseCase->>UseCase: Compute toggled state via ToggleRoutineUseCase
    UseCase->>Repository: applyRoutineToggle(dateKey, routineId, completionInfo)
    Repository->>LocalDS: applyOptimisticToggle(dateKey, routineId, info)
    LocalDS->>LocalDS: Update routineSchedule state optimistically
    LocalDS-->>Repository: State updated
    Repository->>Repository: Record original state, queue pending change
    Repository->>Repository: Emit into debounced sync trigger (500ms)
    
    par Debounce delay
        Note over Repository: Wait 500ms for more toggles
    end
    
    Repository->>Repository: flushAllPendingChanges() triggered
    Repository->>RemoteDS: Sync actual changes (skipping unchanged)
    alt Sync success
        RemoteDS-->>Repository: Return result
        Repository->>Repository: Clear pending queue
    else Sync failure
        RemoteDS-->>Repository: Return error
        Repository->>Repository: Emit syncError
        Repository->>LocalDS: clearCache()
        Repository->>RemoteDS: Refetch and restore (observeWeeklyRoutines)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

이 PR은 아키텍처 수준의 광범위한 리팩터링으로, 다음과 같은 복합적인 요소를 포함합니다:

  • 다중 계층 변경: 도메인, 데이터, 프레젠테이션 계층에 걸친 일관된 패턴 변경
  • 제어 흐름 변환: 이벤트 기반 → Flow 기반 관찰 패턴으로의 대규모 전환
  • 상태 관리 로직: 로컬 캐싱, 낙관적 업데이트, 배치 동기화, 롤백 등 복잡한 상태 동기화 로직
  • 다양한 파일 유형: 인터페이스, 구현체, 유스케이스, ViewModel, 모델, UI 컴포넌트 등 이질적인 변경
  • 테스트 추가: 새로운 테스트 스위트로 변경된 로직 검증 필요
  • API 제거: 여러 공개 API와 이벤트 타입 제거로 인한 영향 범위 분석 필요

리뷰어는 각 계층의 일관성, 특히 Repository와 ViewModel의 상태 관리 로직, 그리고 LocalDataSource의 낙관적 업데이트 구현을 주의 깊게 검토해야 합니다.

Poem

🐰 Flow가 흐르고 SSOT가 솟아,

로컬 캐시에 낙관적 꿈이 피어,

배치 동기화로 부드럽게 이루어,

이벤트 흐름은 관찰로 변신했네! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명에서 필수 섹션들이 대부분 작성되었으나, 스크린샷이 'N/A'로 표시되어 있고 일부 섹션이 미흡합니다. 작업 설명을 더 상세히 작성하고, 관련 네비게이션 변경사항에 대한 명확한 설명이 필요합니다. 스크린샷이 필요한 경우 추가 바랍니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[Refactor/#207] 루틴 데이터 아키텍처 개선'은 변경 사항의 핵심 목표(루틴 데이터 아키텍처를 Flow 기반 SSOT로 개선)를 명확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed PR의 모든 주요 변경 사항(RoutineLocalDataSource 추가, RoutineRepositoryImpl debounce 배치 sync, ToggleStrategy 이동, ViewModel 단순화, 단위 테스트 8개 추가)이 이슈 #207의 목표를 충족합니다.
Out of Scope Changes check ✅ Passed BitnagilConfirmDialog 추가와 WithdrawalConfirmDialog 리팩토링은 UI/UX 개선으로 범위 내이며, 모든 변경이 이슈 #207의 아키텍처 개선 목표 또는 지원 작업과 관련이 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#207-routine-logic

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.

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

🧹 Nitpick comments (2)
data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt (1)

54-56: clearCache에서 lastFetchRange 초기화 누락

clearCache()_routineSchedulenull로 설정하고 lastFetchRange는 유지합니다. 이로 인해 캐시가 무효화되었지만 범위 정보는 남아있는 불일치 상태가 될 수 있습니다.

♻️ lastFetchRange 초기화 추가 제안
 override fun clearCache() {
+    lastFetchRange = null
     _routineSchedule.update { null }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt`
around lines 54 - 56, The clearCache() implementation only nulls
_routineSchedule but leaves lastFetchRange stale; update
RoutineLocalDataSourceImpl.clearCache to also reset the fetch-range state (e.g.,
set lastFetchRange to null or its initial value) at the same time you call
_routineSchedule.update so the cache and its range metadata stay consistent;
reference the symbols clearCache, _routineSchedule, and lastFetchRange and
perform the reset using the same concurrency-safe/state-updating mechanism used
for _routineSchedule.
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt (1)

38-41: init 블록 대신 containeronCreate 콜백 사용을 권장합니다.

HomeViewModel에서는 container(onCreate = { ... })를 사용하여 lifecycle-aware하게 초기화하는 반면, 이 ViewModel은 init 블록을 사용합니다. 이로 인해 container가 구독되기 전에 flow 수집이 시작될 수 있습니다.

♻️ onCreate 콜백 사용 제안
-    override val container: Container<RoutineListState, RoutineListSideEffect> = container(initialState = RoutineListState.INIT)
+    override val container: Container<RoutineListState, RoutineListSideEffect> = container(
+        initialState = RoutineListState.INIT,
+        onCreate = {
+            updateDate(selectedDate)
+            observeWeeklyRoutines()
+        },
+    )

-    init {
-        updateDate(selectedDate)
-        observeWeeklyRoutines()
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt`
around lines 38 - 41, The init block in RoutineListViewModel starts work
immediately (calling updateDate(selectedDate) and observeWeeklyRoutines()) and
can begin collecting flows before the container is subscribed; move that
initialization into the container's onCreate callback so it is lifecycle-aware:
remove the init block and call updateDate(selectedDate) and
observeWeeklyRoutines() from container(onCreate = { ... }) inside
RoutineListViewModel (mirror the pattern used in HomeViewModel) to ensure flows
are collected only after the container is created.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavigator.kt`:
- Around line 33-37: The current navigateTo(route: HomeRoute) uses
navController.navigate(route) with popUpTo(0) which clears the entire back stack
and prevents per-tab state preservation; replace that behavior by navigating
with a popUpTo targeting the nav graph's start destination (use
navController.graph.findStartDestination().id) and enable saveState = true
inside the popUpTo block, add launchSingleTop = true on the navigate options,
and set restoreState = true so each Bottom Navigation tab preserves and restores
its own back stack and UI state instead of wiping it on every tab switch.

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt`:
- Around line 9-13: clearCache currently only resets the in-memory schedule but
leaves lastFetchRange set, causing stale range-based cache hits; update
RoutineLocalDataSourceImpl.clearCache() so it also sets lastFetchRange = null
when you reset _routineSchedule (i.e., clear both the schedule and the
lastFetchRange fields in the clearCache implementation to maintain cache
consistency).

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt`:
- Around line 16-22: lastFetchRange is a single mutable var causing cache
inconsistency when multiple collectors (e.g., HomeViewModel and
RoutineListViewModel) call observeWeeklyRoutines with different ranges; change
the shared single-range cache to a range-keyed cache or per-consumer tracking:
replace lastFetchRange and the single _routineSchedule state usage in
RoutineLocalDataSourceImpl with a Map<Pair<String,String>, RoutineSchedule> (or
track ranges per-collector ID) and update saveSchedule, refreshCache, and
observeWeeklyRoutines to read/write by the specific range key so concurrent
observers don’t overwrite each other’s cached ranges and data.

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt`:
- Around line 84-96: The rollback uses routineLocalDataSource.lastFetchRange
which can differ from the week that dateKey refers to; update
flushPendingChanges to compute or retrieve the correct rollback range for the
given dateKey instead of relying on lastFetchRange — e.g., calculate the week
range from dateKey (or maintain a map like originalFetchRangeByDate keyed by
dateKey when optimistic updates are created) and call fetchAndSave(rangeStart,
rangeEnd) with that range; ensure the code paths around pendingChangesByDate,
originalStatesByDate and the onFailure handler use that computed/retrieved range
to rollback the exact week for the dateKey.
- Around line 39-44: The mutableMapOf instances pendingChangesByDate and
originalStatesByDate in RoutineRepositoryImpl are not thread-safe and are
concurrently accessed by applyRoutineToggle (called from multiple ViewModels)
and flushPendingChanges (running on repositoryScope); replace them with
thread-safe structures or add synchronization: either change both to
ConcurrentHashMap<String, ConcurrentHashMap<String, RoutineCompletionInfo>>
(ensure inner maps are concurrent too) or introduce a Mutex used to wrap every
read/write/iteration in applyRoutineToggle and flushPendingChanges so all
accesses are protected; update any code that iterates or updates nested maps to
use thread-safe patterns (e.g., computeIfAbsent on concurrent maps or withLock
on the Mutex) to avoid ConcurrentModificationException and data loss.
- Around line 55-65: The current observeWeeklyRoutines flow mutates a single
shared routineLocalDataSource.lastFetchRange and calls
clearCache()/fetchAndSave(startDate, endDate), causing cache thrashing when
multiple subscribers request different ranges; change observeWeeklyRoutines to
provide per-range isolation by keying flows by (startDate,endDate) instead of
using the single lastFetchRange: introduce a map (e.g.,
MutableMap<Pair<String,String>, StateFlow<RoutineSchedule?>> or SharedFlow)
inside RoutineRepositoryImpl that returns or creates a dedicated flow per range,
call fetchAndSave(range) and update only that range's state (or have
routineLocalDataSource expose per-range storage/fetch methods), and remove
dependence on routineLocalDataSource.lastFetchRange/clearCache so concurrent
subscribers for different ranges do not clobber each other.

In
`@domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ApplyRoutineToggleUseCase.kt`:
- Around line 12-17: ApplyRoutineToggleUseCase.invoke currently trusts
UI-snapshot params (isCompleted, subRoutineCompletionStates) which can be stale;
move the toggle calculation into the data layer so the final state is derived
from the source-of-truth atomically. Change ApplyRoutineToggleUseCase.invoke to
pass only identity (dateKey, routineId) and the toggle intent/strategy to
RoutineLocalDataSource (or a new method on it), and implement an atomic
read-modify-write in RoutineLocalDataSource that reads the latest completion
state, computes the new isCompleted and subRoutineCompletionStates (including
the sub-routine toggling logic), persists the result, and returns the updated
state; remove reliance on the passed snapshot params from
ApplyRoutineToggleUseCase and ensure callers send only the minimal intent.

In
`@presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt`:
- Around line 73-82: The observeWeeklyRoutines function is missing the
repeatOnSubscription wrapper and error handling; wrap the flow collection inside
repeatOnSubscription { ... } (same pattern as HomeViewModel) and add a .catch {
e -> reduce { state.copy(isLoading = false) /* optionally log or signal error */
} } before collect so exceptions don't propagate and loading state is cleared;
update references in the block to observeWeeklyRoutinesUseCase(...) and the
existing reduce calls (state.copy(...)) accordingly.

---

Nitpick comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt`:
- Around line 54-56: The clearCache() implementation only nulls _routineSchedule
but leaves lastFetchRange stale; update RoutineLocalDataSourceImpl.clearCache to
also reset the fetch-range state (e.g., set lastFetchRange to null or its
initial value) at the same time you call _routineSchedule.update so the cache
and its range metadata stay consistent; reference the symbols clearCache,
_routineSchedule, and lastFetchRange and perform the reset using the same
concurrency-safe/state-updating mechanism used for _routineSchedule.

In
`@presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt`:
- Around line 38-41: The init block in RoutineListViewModel starts work
immediately (calling updateDate(selectedDate) and observeWeeklyRoutines()) and
can begin collecting flows before the container is subscribed; move that
initialization into the container's onCreate callback so it is lifecycle-aware:
remove the init block and call updateDate(selectedDate) and
observeWeeklyRoutines() from container(onCreate = { ... }) inside
RoutineListViewModel (mirror the pattern used in HomeViewModel) to ensure flows
are collected only after the container is created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 99e5ef16-a1fa-4233-9ecb-fd494c4deed2

📥 Commits

Reviewing files that changed from the base of the PR and between 9d266a9 and d4abc0e.

📒 Files selected for processing (25)
  • app/src/main/java/com/threegap/bitnagil/di/data/CoroutineModule.kt
  • app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
  • app/src/main/java/com/threegap/bitnagil/navigation/home/HomeBottomNavigationBar.kt
  • app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
  • app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavigator.kt
  • app/src/main/java/com/threegap/bitnagil/navigation/home/HomeRoute.kt
  • data/src/main/java/com/threegap/bitnagil/data/di/CoroutineQualifier.kt
  • data/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.kt
  • data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt
  • data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt
  • data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
  • data/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/model/OnBoardingRecommendRoutineEvent.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetOnBoardingRecommendRoutineEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/model/ToggleStrategy.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/model/WriteRoutineEvent.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ApplyRoutineToggleUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/GetWriteRoutineEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveWeeklyRoutinesUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/RoutineCompletionUseCase.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt
💤 Files with no reviewable changes (7)
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/model/OnBoardingRecommendRoutineEvent.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/GetWriteRoutineEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetOnBoardingRecommendRoutineEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/RoutineCompletionUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/model/WriteRoutineEvent.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt

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

♻️ Duplicate comments (1)
data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt (1)

62-71: ⚠️ Potential issue | 🟠 Major

여전히 단일 주차 캐시만 지원합니다.

단일 lastFetchRange와 공용 routineSchedule 하나만 쓰는 구조에서 Line 64의 clearCache()가 다른 collector에게도 그대로 전파됩니다. PR 목표대로 HomeViewModel과 RoutineListViewModel이 서로 다른 주차를 동시에 구독하면 한쪽이 다른 쪽 데이터를 덮어쓰고, null을 본 기존 collector가 자기 범위를 다시 fetch하면서 cache thrashing까지 발생합니다. 아래 rollback/refresh 경로도 같은 lastFetchRange에 묶여 있어 잘못된 주차를 다시 당길 수 있습니다. 범위별 캐시와 flow를 분리해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt`
around lines 62 - 71, Current implementation uses a single shared lastFetchRange
and routineSchedule so clearCache() and null emissions affect all collectors;
change to a range-keyed cache/flow: replace
routineLocalDataSource.lastFetchRange and routineLocalDataSource.routineSchedule
with a Map<Pair<String,String>, RoutineSchedule?> (and/or a Map to
StateFlow<RoutineSchedule?>) keyed by (startDate to endDate), update
observeWeeklyRoutines to look up or create the per-range StateFlow for the given
key (do not call global clearCache()), call a per-key clear or refresh only for
that key, and ensure fetchAndSave(startDate, endDate) writes the result into the
map entry/StateFlow for that key so multiple collectors can subscribe to
different weeks without stomping each other.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilConfirmDialog.kt`:
- Around line 24-39: The BitnagilConfirmDialog currently forwards onConfirm to
BasicAlertDialog's onDismissRequest which causes dismiss actions (back
press/outside touch) to trigger confirm; add a new parameter onDismiss: () ->
Unit (defaulting to empty or a no-op) to BitnagilConfirmDialog and pass
onDismiss to BasicAlertDialog's onDismissRequest while keeping onConfirm for the
confirm button handler (e.g., the existing onConfirm lambda used only for the
positive action). Update function signature (BitnagilConfirmDialog) and internal
call sites to use onDismissRequest = onDismiss and ensure any callers are
adjusted or rely on the default no-op to preserve backwards compatibility.

In
`@data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt`:
- Around line 74-77: fetchAndSave currently throws exceptions (throw it) which
terminates flows and breaks Result<Unit> contracts; change fetchAndSave to
return Result<Unit> (or Result<...> as appropriate) instead of throwing, use
Result.success on success and Result.failure on failures, and remove direct
throw; update all callers—observeWeeklyRoutines, refreshCache,
flushAllPendingChanges and the .also invocations inside
deleteRoutine/registerRoutine/editRoutine/deleteRoutineForDay—to explicitly
handle the Result (log or emit an error state, convert to a failed Result<Unit>
for caller APIs, and avoid rethrowing) so the debounce collector and retry logic
aren’t interrupted and Result<Unit> APIs preserve their contract.

---

Duplicate comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt`:
- Around line 62-71: Current implementation uses a single shared lastFetchRange
and routineSchedule so clearCache() and null emissions affect all collectors;
change to a range-keyed cache/flow: replace
routineLocalDataSource.lastFetchRange and routineLocalDataSource.routineSchedule
with a Map<Pair<String,String>, RoutineSchedule?> (and/or a Map to
StateFlow<RoutineSchedule?>) keyed by (startDate to endDate), update
observeWeeklyRoutines to look up or create the per-range StateFlow for the given
key (do not call global clearCache()), call a per-key clear or refresh only for
that key, and ensure fetchAndSave(startDate, endDate) writes the result into the
map entry/StateFlow for that key so multiple collectors can subscribe to
different weeks without stomping each other.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5319d0e7-5e79-4f2e-8d71-f28129c89017

📥 Commits

Reviewing files that changed from the base of the PR and between d4abc0e and e7a6b02.

📒 Files selected for processing (9)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilConfirmDialog.kt
  • data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
  • data/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveRoutineSyncErrorUseCase.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/withdrawal/component/WithdrawalConfirmDialog.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.kt
  • data/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt

@wjdrjs00 wjdrjs00 requested a review from l5x5l March 27, 2026 05:45
@wjdrjs00 wjdrjs00 added 🔨 Refactor 기존 기능 개선 🧤 대현 labels Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 Refactor 기존 기능 개선 🧤 대현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 루틴 데이터 아키텍처 개선

1 participant