Conversation
Walkthrough루틴 데이터 아키텍처를 Flow 기반 SSOT(Single Source of Truth)로 전환하고, 낙관적 업데이트와 배치 동기화 책임을 데이터 계층으로 이전했습니다. HomeRoute를 enum에서 sealed interface로 변경하고, 이벤트 기반 제어 흐름을 관찰 기반으로 리팩터링했습니다. Changes
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 이 PR은 아키텍처 수준의 광범위한 리팩터링으로, 다음과 같은 복합적인 요소를 포함합니다:
리뷰어는 각 계층의 일관성, 특히 Repository와 ViewModel의 상태 관리 로직, 그리고 LocalDataSource의 낙관적 업데이트 구현을 주의 깊게 검토해야 합니다. Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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.
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()가_routineSchedule만null로 설정하고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블록 대신container의onCreate콜백 사용을 권장합니다.
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
📒 Files selected for processing (25)
app/src/main/java/com/threegap/bitnagil/di/data/CoroutineModule.ktapp/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.ktapp/src/main/java/com/threegap/bitnagil/navigation/home/HomeBottomNavigationBar.ktapp/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.ktapp/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavigator.ktapp/src/main/java/com/threegap/bitnagil/navigation/home/HomeRoute.ktdata/src/main/java/com/threegap/bitnagil/data/di/CoroutineQualifier.ktdata/src/main/java/com/threegap/bitnagil/data/onboarding/repositoryImpl/OnBoardingRepositoryImpl.ktdata/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.ktdata/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.ktdata/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.ktdata/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.ktdomain/src/main/java/com/threegap/bitnagil/domain/onboarding/model/OnBoardingRecommendRoutineEvent.ktdomain/src/main/java/com/threegap/bitnagil/domain/onboarding/repository/OnBoardingRepository.ktdomain/src/main/java/com/threegap/bitnagil/domain/onboarding/usecase/GetOnBoardingRecommendRoutineEventFlowUseCase.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/model/ToggleStrategy.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/model/WriteRoutineEvent.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ApplyRoutineToggleUseCase.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/GetWriteRoutineEventFlowUseCase.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveWeeklyRoutinesUseCase.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/RoutineCompletionUseCase.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.ktpresentation/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
app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavigator.kt
Show resolved
Hide resolved
data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt
Show resolved
Hide resolved
...rc/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt
Show resolved
Hide resolved
data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
Show resolved
Hide resolved
data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
Show resolved
Hide resolved
data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt
Outdated
Show resolved
Hide resolved
domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ApplyRoutineToggleUseCase.kt
Show resolved
Hide resolved
.../src/main/java/com/threegap/bitnagil/presentation/screen/routinelist/RoutineListViewModel.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
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
📒 Files selected for processing (9)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilConfirmDialog.ktdata/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.ktdata/src/test/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImplTest.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.ktdomain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ObserveRoutineSyncErrorUseCase.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeScreen.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.ktpresentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/contract/HomeState.ktpresentation/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
[ PR Content ]
루틴 데이터도 Flow 기반 ssot 방식으로 변경하고, Optimistic Update 책임을 data 계층으로 변경했습니다.
Related issue
Screenshot 📸
Work Description
RoutineLocalDataSource추가로 루틴 데이터의 SSOT(StateFlow) 구축RoutineRepositoryImpl에서debounce 배치 sync + rollback 처리 ->HomeViewModel의 책임 제거HomeViewModel/RoutineListViewModel모두 동일한 Repository Flow를 구독해 자동 갱신@IoDispatcher주입으로 테스트 가능성 확보 후 단위 테스트 8개 작성To Reviewers 📢
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
개선 사항