Skip to content

[Refactor/#192] DailyEmotion 데이터 흐름 개선#193

Open
wjdrjs00 wants to merge 5 commits intodevelopfrom
refactor/#192-daily-emotion-flow
Open

[Refactor/#192] DailyEmotion 데이터 흐름 개선#193
wjdrjs00 wants to merge 5 commits intodevelopfrom
refactor/#192-daily-emotion-flow

Conversation

@wjdrjs00
Copy link
Member

@wjdrjs00 wjdrjs00 commented Mar 8, 2026

[ PR Content ]

스크럼 시간에 언급했던 flow 기반의 데이터 스트림 방식을 DailyEmotion(오늘의 감정) 부분에 먼저 적용을 해봤습니다.
작업 문서는 노션에 띠로 정리했습니다.! -> 🔗 노션 링크

Related issue

Screenshot 📸

  • N/A

Work Description

  • EmotionRepositoryImpl.kt: StateFlow 도입, onSubscription 및 중복 호출 방지 로직 적용
  • DailyEmotion.kt: fetchedDate 필드 및 isStale 속성 추가를 통한 상태 판단 로직 내재화
  • HomeViewModel.kt / RecommendRoutineViewModel.kt: 명령형 로직 제거 및 Flow 관찰 방식으로 리팩터링
  • 삭제: EmotionChangeEvent.kt, FetchDailyEmotionUseCase.kt, GetEmotionChangeEventFlowUseCase.kt (레거시 제거)

To Reviewers 📢

  • 궁금한 사항이 있다면 리뷰 남겨주세요!

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 일일 감정 데이터를 스트림(구독)으로 관찰하는 API가 도입되었습니다.
  • 개선 사항

    • 구독 시 자동으로 최신 데이터를 가져와 UI에 즉시 반영됩니다.
    • 캐시 관리 및 무결성 향상(동시성 보호, 캐시 무효화 지원).
    • 감정 등록 시 최신화를 자동으로 트리거하도록 동작이 개선되었습니다.
    • 일일 데이터에 조회(fetchedDate) 정보와 신선도(isStale) 판단 기능이 추가되었습니다.
  • 제거

    • 기존의 이벤트 기반 감정 변경 알림 경로 및 관련 사용 사례가 삭제되었습니다.

@wjdrjs00 wjdrjs00 requested a review from l5x5l March 8, 2026 17:47
@wjdrjs00 wjdrjs00 self-assigned this Mar 8, 2026
@wjdrjs00 wjdrjs00 added 🔨 Refactor 기존 기능 개선 🧤 대현 labels Mar 8, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

Walkthrough

이벤트 기반 감정 갱신을 제거하고 StateFlow 기반 관찰로 전환했습니다. DailyEmotion에 fetchedDate/isStale가 추가되고, 저장소는 원격/로컬 분리, Mutex 동기화, 구독 시 자동 페치·캐시 로직을 구현합니다. ViewModel들은 ObserveDailyEmotionUseCase로 전환되었습니다.

Changes

Cohort / File(s) Summary
도메인 모델
domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/DailyEmotion.kt
fetchedDate: LocalDate 추가, isStale(today: LocalDate) 메서드 및 companion object { INIT } 추가.
응답 매핑
data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/DailyEmotionResponse.kt
toDomain() 시그니처가 toDomain(fetchedDate: LocalDate)로 변경되어 fetchedDate를 도메인 모델로 전달하도록 수정.
저장소 구현
data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt
원격/로컬 데이터소스 분리, observeDailyEmotion(): Flow<Result<DailyEmotion>> 추가, fetchAndSaveDailyEmotion(today, forceRefresh)Mutex 동기화 도입, 이벤트 타입/SharedFlow 제거, clearCache() 추가. 생성자 의존성도 Remote/Local로 변경.
데이터소스 인터페이스
data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionRemoteDataSource.kt, data/.../datasource/EmotionLocalDataSource.kt
원격/로컬 인터페이스 분리(이름 변경: EmotionRemoteDataSource) 및 로컬 인터페이스에 val dailyEmotion: StateFlow<DailyEmotion?>, saveDailyEmotion, clearCache 추가.
데이터소스 구현
data/.../datasourceImpl/EmotionRemoteDataSourceImpl.kt, data/.../datasourceImpl/EmotionLocalDataSourceImpl.kt
원격 구현명 변경(EmotionRemoteDataSourceImpl) 및 로컬 구현 추가(EmotionLocalDataSourceImpl)로 StateFlow 기반 캐시와 저장/클리어 구현 추가.
의존성 주입
app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
단일 바인딩을 로컬/원격 바인딩으로 분리: bindEmotionLocalDataSource(...) 및 신규 bindEmotionRemoteDataSource(...) 추가.
도메인 레포지토리/유스케이스
domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt, domain/.../usecase/ObserveDailyEmotionUseCase.kt
fetchDailyEmotion 및 이벤트 흐름 제거, observeDailyEmotion(): Flow<Result<DailyEmotion>>clearCache() 추가. FetchDailyEmotionUseCaseObserveDailyEmotionUseCase로 변경(Flow 반환).
제거된 이벤트/유스케이스
domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt, domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt
이벤트 타입 및 관련 이벤트 유스케이스 삭제.
프레젠테이션 변경
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt, presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt
ViewModel들이 ObserveDailyEmotionUseCase를 주입받아 StateFlow/Flow 구독으로 전환, 기존 이벤트 기반 로직 제거 및 container 빌드설정 조정.

Sequence Diagram(s)

sequenceDiagram
    participant VM as ViewModel
    participant UC as ObserveDailyEmotionUseCase
    participant Repo as EmotionRepositoryImpl
    participant Remote as EmotionRemoteDataSource
    participant Local as EmotionLocalDataSource

    rect rgba(135,206,250,0.5)
    VM->>UC: 구독 시작
    end

    UC->>Repo: observeDailyEmotion() 구독
    Note right of Repo: onSubscription 트리거
    Repo->>Repo: Mutex로 동시성 검사
    alt 캐시가 없거나 isStale 또는 forceRefresh
        Repo->>Remote: 원격 API 호출
        Remote-->>Repo: 응답(감정 데이터)
        Repo->>Local: saveDailyEmotion(DailyEmotion with fetchedDate)
        Local-->>Repo: StateFlow 업데이트
        Repo-->>UC: Flow로 Result 발행
        UC-->>VM: UI 업데이트
    else 캐시가 최신
        Local-->>Repo: 현재 캐시값 반환
        Repo-->>UC: Flow로 Result 발행 (네트워크 없음)
        UC-->>VM: UI 업데이트
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 깡총깡총 코드 숲을 달려,
StateFlow로 새벽을 맞이했네.
캐시를 품고, 동시성 지키며,
모든 화면에 감정이 반짝이네. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 핵심을 명확하게 요약하고 있으며, DailyEmotion의 데이터 흐름 개선이라는 주요 변경 내용을 정확히 반영합니다.
Description check ✅ Passed PR 설명이 관련 이슈 링크, 작업 설명, 리뷰 요청 등 필수 항목을 포함하고 있으며 변경 사항을 구체적으로 기술하고 있습니다.
Linked Issues check ✅ Passed 모든 이슈 요구사항이 충족되었습니다: StateFlow 기반 dailyEmotionFlow 도입, fetchedDate 및 isStale 속성 추가, onSubscription을 통한 자동 로드, Mutex 기반 동시성 제어, 레거시 API 제거, ObserveDailyEmotionUseCase 도입이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #192 이슈의 범위 내에 있습니다: DailyEmotion 데이터 흐름 개선, 의존성 분리, 레거시 제거 등이 모두 명시된 목표와 일치합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#192-daily-emotion-flow

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

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/DailyEmotionResponse.kt (1)

21-28: ⚠️ Potential issue | 🟠 Major

fetchedDate는 변환 시각이 아니라 요청 기준 일자를 사용해야 합니다.

27번 줄에서 LocalDate.now()를 호출하면, EmotionRepositoryImpl.kt 46번 줄에서 고정한 요청 일자와 어긋날 수 있습니다. 자정 전후에 응답이 돌아오면 전날 데이터를 받아놓고도 fetchedDate가 오늘 날짜로 기록되어 isStalefalse가 되고, 다음 구독에서 재조회가 차단됩니다. 요청에 사용한 LocalDatetoDomain(requestedDate: LocalDate) 매개변수로 전달하는 방식이 안전합니다.

수정 예시
-fun DailyEmotionResponse.toDomain(): DailyEmotion =
+fun DailyEmotionResponse.toDomain(fetchedDate: LocalDate): DailyEmotion =
     DailyEmotion(
         type = emotionMarbleType,
         name = emotionMarbleName,
         imageUrl = imageUrl,
         homeMessage = emotionMarbleHomeMessage,
-        fetchedDate = LocalDate.now(),
+        fetchedDate = fetchedDate,
     )
-            val currentDate = LocalDate.now().toString()
-            emotionDataSource.fetchDailyEmotion(currentDate).map {
-                _dailyEmotionFlow.value = it.toDomain()
+            val requestedDate = LocalDate.now()
+            emotionDataSource.fetchDailyEmotion(requestedDate.toString()).map {
+                _dailyEmotionFlow.value = it.toDomain(requestedDate)
             }
🤖 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/emotion/model/response/DailyEmotionResponse.kt`
around lines 21 - 28, DailyEmotionResponse.toDomain currently sets fetchedDate =
LocalDate.now(), which can diverge from the request date used in
EmotionRepositoryImpl and cause stale-check bugs; change the conversion to
accept a requestedDate parameter (e.g., fun
DailyEmotionResponse.toDomain(requestedDate: LocalDate)): construct DailyEmotion
with fetchedDate = requestedDate, update all callers (notably where
EmotionRepositoryImpl maps responses at the request site) to pass the same
requestedDate used for the request, and ensure the DailyEmotion data class usage
remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt`:
- Around line 20-25: The current compareAndSet in isFetching causes a concurrent
fetch to short-circuit and return Result.success(Unit), which can drop a
subsequent refresh triggered by registerEmotion() or a subscription's initial
fetch; change the fetch control in fetchDailyEmotion() (the routine guarded by
isFetching.compareAndSet) so that when a fetch is already in progress it either
queues a single follow-up fetch or serializes requests: e.g., replace the
immediate success return with a flag/queue (e.g., pendingRefresh boolean) that
is set when a fetch is in progress and a new fetch is requested, and after the
in-flight fetch completes have the code check pendingRefresh and perform one
additional fetch if needed; update references to isFetching, compareAndSet,
fetchDailyEmotion(), and registerEmotion() to use this handshake so a trailing
refresh is not lost.

---

Outside diff comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/DailyEmotionResponse.kt`:
- Around line 21-28: DailyEmotionResponse.toDomain currently sets fetchedDate =
LocalDate.now(), which can diverge from the request date used in
EmotionRepositoryImpl and cause stale-check bugs; change the conversion to
accept a requestedDate parameter (e.g., fun
DailyEmotionResponse.toDomain(requestedDate: LocalDate)): construct DailyEmotion
with fetchedDate = requestedDate, update all callers (notably where
EmotionRepositoryImpl maps responses at the request site) to pass the same
requestedDate used for the request, and ensure the DailyEmotion data class usage
remains consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 52f22984-cc82-4ed7-bda4-56d2b9b2db63

📥 Commits

Reviewing files that changed from the base of the PR and between 55c8f13 and ead9462.

📒 Files selected for processing (9)
  • data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/DailyEmotionResponse.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/DailyEmotion.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/ObserveDailyEmotionUseCase.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt
💤 Files with no reviewable changes (2)
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt

Copy link
Contributor

@l5x5l l5x5l left a comment

Choose a reason for hiding this comment

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

Event를 각 ViewModel에서 감지해 감정 데이터 조회를 호출하는 공통 로직을 제거한 것은 좋아 보입니다! 👍
다만 수정해주신 코드에서 DailyEmotionResponse의 toDomain이나 DailyEmotion의 isStale 같이 LocalDate를 사용하는 부분을 보면 해당 정보를 인자로 전달받아 사용하기 보다는 함수 내부에서 매번 LocalData.now()를 호출하고 있어 호출 시점마다 결과가 달라질 수 있는 비결정적 코드라는 느낌을 받았습니다...!
이 부분만 외부에서 LocalDate를 주입받아 사용할 수 있도록 변경해주시면 추후 테스트시 또는 기능 유지보수시에 더 좋을 것 같아요!

@wjdrjs00
Copy link
Member Author

wjdrjs00 commented Mar 9, 2026

Event를 각 ViewModel에서 감지해 감정 데이터 조회를 호출하는 공통 로직을 제거한 것은 좋아 보입니다! 👍 다만 수정해주신 코드에서 DailyEmotionResponse의 toDomain이나 DailyEmotion의 isStale 같이 LocalDate를 사용하는 부분을 보면 해당 정보를 인자로 전달받아 사용하기 보다는 함수 내부에서 매번 LocalData.now()를 호출하고 있어 호출 시점마다 결과가 달라질 수 있는 비결정적 코드라는 느낌을 받았습니다...! 이 부분만 외부에서 LocalDate를 주입받아 사용할 수 있도록 변경해주시면 추후 테스트시 또는 기능 유지보수시에 더 좋을 것 같아요!

수정완료! -> 3edbfd1

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

20-25: ⚠️ Potential issue | 🟠 Major

진행 중 fetch를 성공으로 단락하면 최신 감정 갱신이 유실됩니다.

Line 44에서 이미 fetch 중이면 바로 Result.success(Unit)을 반환해서, Line 39의 후행 refresh가 Lines 23-25의 초기 구독 fetch와 겹칠 때 그대로 드롭됩니다. 첫 요청이 등록 이전 상태를 읽었다면 _dailyEmotionFlow는 같은 날짜 동안 stale로 간주되지 않아 오래된 값을 유지할 수 있습니다. 중복 호출만 막지 말고, 진행 중 요청 뒤에 refresh 1회를 보장하도록 직렬화하거나 pending 플래그를 두는 쪽이 안전합니다.

예시 수정안
-import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.sync.Mutex

-    private val isFetching = AtomicBoolean(false)
+    private val fetchMutex = Mutex()
+    `@Volatile`
+    private var pendingRefresh = false

     override suspend fun fetchDailyEmotion(): Result<Unit> {
-        if (!isFetching.compareAndSet(false, true)) return Result.success(Unit)
+        if (!fetchMutex.tryLock()) {
+            pendingRefresh = true
+            return Result.success(Unit)
+        }
         return try {
-            val today = LocalDate.now()
-            emotionDataSource.fetchDailyEmotion(today.toString()).map {
-                _dailyEmotionFlow.value = it.toDomain(today)
-            }
+            var result: Result<Unit>
+            do {
+                pendingRefresh = false
+                val today = LocalDate.now()
+                result = emotionDataSource.fetchDailyEmotion(today.toString()).map {
+                    _dailyEmotionFlow.value = it.toDomain(today)
+                }
+            } while (pendingRefresh)
+            result
         } finally {
-            isFetching.set(false)
+            fetchMutex.unlock()
         }
     }

Also applies to: 39-52

🤖 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/emotion/repositoryImpl/EmotionRepositoryImpl.kt`
around lines 20 - 25, The current concurrency handling in EmotionRepositoryImpl
uses isFetching to short-circuit fetchDailyEmotion and returns early, which can
drop a later refresh requested from onSubscription and leave _dailyEmotionFlow
stale; update fetchDailyEmotion (and related callers) so that when isFetching is
true you record a pending-refresh flag (or enqueue one) instead of returning
success immediately, and when the in-flight fetch completes the code checks that
flag to run exactly one additional fetch (or emit a refresh) and clear it;
reference isFetching, fetchDailyEmotion, _dailyEmotionFlow and dailyEmotionFlow
to locate where to add the pending flag/serialization and ensure
onSubscription-triggered fetches are not lost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt`:
- Around line 20-25: The current concurrency handling in EmotionRepositoryImpl
uses isFetching to short-circuit fetchDailyEmotion and returns early, which can
drop a later refresh requested from onSubscription and leave _dailyEmotionFlow
stale; update fetchDailyEmotion (and related callers) so that when isFetching is
true you record a pending-refresh flag (or enqueue one) instead of returning
success immediately, and when the in-flight fetch completes the code checks that
flag to run exactly one additional fetch (or emit a refresh) and clear it;
reference isFetching, fetchDailyEmotion, _dailyEmotionFlow and dailyEmotionFlow
to locate where to add the pending flag/serialization and ensure
onSubscription-triggered fetches are not lost.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b5f7366c-1d2e-4fa6-aeff-8c044aa64d10

📥 Commits

Reviewing files that changed from the base of the PR and between ead9462 and 3edbfd1.

📒 Files selected for processing (3)
  • data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/DailyEmotionResponse.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/DailyEmotion.kt


private val _emotionChangeEventFlow = MutableSharedFlow<EmotionChangeEvent>()
override suspend fun getEmotionChangeEventFlow(): Flow<EmotionChangeEvent> = _emotionChangeEventFlow.asSharedFlow()
override suspend fun fetchDailyEmotion(): Result<Unit> {
Copy link
Contributor

Choose a reason for hiding this comment

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

[2] 이 함수에서도 LocalDate 인자 추가 부탁드립니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

요부분 수정하면서 #195 <- 요 pr과 방식을 통일하기 위해 datasoure 분리 작업도 같이 진행해보겠습니다~

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt (1)

32-37: 실패 시에도 loadRecommendRoutines()가 호출됩니다

observeDailyEmotionUseCase()Result.failure를 emit하면 getOrNull()?.typenull을 반환하고, 이 null 값도 distinctUntilChanged()를 통과하여 loadRecommendRoutines()를 트리거합니다.

의도된 동작이라면 문제없지만, 실패 시 불필요한 API 호출을 방지하려면 성공 케이스만 필터링하는 것을 고려해 보세요.

♻️ 실패 케이스 필터링 예시
 repeatOnSubscription {
     observeDailyEmotionUseCase()
+        .filter { it.isSuccess }
         .map { result -> result.getOrNull()?.type }
         .distinctUntilChanged()
         .collect { loadRecommendRoutines() }
 }
🤖 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/recommendroutine/RecommendRoutineViewModel.kt`
around lines 32 - 37, The current flow in repeatOnSubscription uses
observeDailyEmotionUseCase() and maps Result to result.getOrNull()?.type so a
failure becomes null and still passes through distinctUntilChanged(), causing
loadRecommendRoutines() to run on errors; update the chain to only forward
successful emissions (e.g., filter for success or mapNotNull the Result to its
type) before distinctUntilChanged() so that loadRecommendRoutines() is invoked
only when a non-null/successful emotion type is emitted from
observeDailyEmotionUseCase().
data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionLocalDataSource.kt (1)

3-9: 데이터 레이어에서 도메인 모델 직접 참조

데이터 레이어(EmotionLocalDataSource)가 도메인 레이어의 DailyEmotion을 직접 import하고 있습니다. Clean Architecture 원칙상 데이터 레이어는 도메인 레이어에 의존하지 않는 것이 이상적이나, 로컬 캐시에서 별도 DTO 변환 오버헤드를 피하기 위한 실용적 선택으로 보입니다.

현재 구조가 프로젝트 전반에서 일관되게 사용된다면 문제없습니다.

🤖 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/emotion/datasource/EmotionLocalDataSource.kt`
around lines 3 - 9, EmotionLocalDataSource currently imports and exposes the
domain model DailyEmotion, coupling the data layer to the domain; to fix this
create a data-layer DTO/entity (e.g., DailyEmotionEntity) and change the
EmotionLocalDataSource signature to use that DTO (replace val dailyEmotion:
StateFlow<DailyEmotion?> and fun saveDailyEmotion(dailyEmotion: DailyEmotion)
with the DTO types), then implement mapping functions between DailyEmotionEntity
and com.threegap.bitnagil.domain.emotion.model.DailyEmotion in the repository or
a mapper class so conversion happens at the boundary (keep method names
saveDailyEmotion and clearCache but update their parameter/return types and add
mappers).
presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt (1)

59-72: 에러 핸들링이 누락되어 있습니다.

onFailure 블록이 비어 있어 에러가 무시됩니다. 이 파일의 다른 에러 핸들러(Line 265, 282)는 Log.e()로 로깅하고 있어 일관성이 떨어집니다. 디버깅을 위해 최소한 로깅을 추가하는 것이 좋습니다.

♻️ 에러 로깅 추가 제안
 private fun observeDailyEmotion() {
     intent {
         repeatOnSubscription {
             observeDailyEmotionUseCase().collect { result ->
                 result.fold(
                     onSuccess = { dailyEmotion ->
                         reduce { state.copy(dailyEmotion = dailyEmotion.toUiModel()) }
                     },
-                    onFailure = {},
+                    onFailure = { error ->
+                        Log.e("HomeViewModel", "감정 정보 가져오기 실패: ${error.message}")
+                    },
                 )
             }
         }
     }
 }
🤖 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/home/HomeViewModel.kt`
around lines 59 - 72, The onFailure branch in observeDailyEmotion() currently
swallows errors; update the onFailure handler of
observeDailyEmotionUseCase().collect to log the throwable (use Log.e with a
clear tag/message similar to other handlers) and optionally update state or show
an error UI if needed; locate the onFailure lambda in observeDailyEmotion() and
replace the empty block with a Log.e(...) call referencing the throwable so
failures aren't ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt`:
- Line 22: Rename the misspelled constructor parameter and all usages from
emotionLocalDateSource to emotionLocalDataSource in EmotionRepositoryImpl:
update the primary constructor parameter name and every reference inside methods
(e.g., calls currently referencing emotionLocalDateSource) to the correct
emotionLocalDataSource identifier so the class compiles and follows proper
naming.

---

Nitpick comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionLocalDataSource.kt`:
- Around line 3-9: EmotionLocalDataSource currently imports and exposes the
domain model DailyEmotion, coupling the data layer to the domain; to fix this
create a data-layer DTO/entity (e.g., DailyEmotionEntity) and change the
EmotionLocalDataSource signature to use that DTO (replace val dailyEmotion:
StateFlow<DailyEmotion?> and fun saveDailyEmotion(dailyEmotion: DailyEmotion)
with the DTO types), then implement mapping functions between DailyEmotionEntity
and com.threegap.bitnagil.domain.emotion.model.DailyEmotion in the repository or
a mapper class so conversion happens at the boundary (keep method names
saveDailyEmotion and clearCache but update their parameter/return types and add
mappers).

In
`@presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt`:
- Around line 59-72: The onFailure branch in observeDailyEmotion() currently
swallows errors; update the onFailure handler of
observeDailyEmotionUseCase().collect to log the throwable (use Log.e with a
clear tag/message similar to other handlers) and optionally update state or show
an error UI if needed; locate the onFailure lambda in observeDailyEmotion() and
replace the empty block with a Log.e(...) call referencing the throwable so
failures aren't ignored.

In
`@presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt`:
- Around line 32-37: The current flow in repeatOnSubscription uses
observeDailyEmotionUseCase() and maps Result to result.getOrNull()?.type so a
failure becomes null and still passes through distinctUntilChanged(), causing
loadRecommendRoutines() to run on errors; update the chain to only forward
successful emissions (e.g., filter for success or mapNotNull the Result to its
type) before distinctUntilChanged() so that loadRecommendRoutines() is invoked
only when a non-null/successful emotion type is emitted from
observeDailyEmotionUseCase().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a32afea7-6919-4ad1-bfa5-8b2c6ecd8cd1

📥 Commits

Reviewing files that changed from the base of the PR and between 3edbfd1 and 9d4add3.

📒 Files selected for processing (10)
  • app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionLocalDataSource.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionRemoteDataSource.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionLocalDataSourceImpl.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionRemoteDataSourceImpl.kt
  • data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/ObserveDailyEmotionUseCase.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt (1)

79-81: toDomain(today) 중복 호출 개선 필요

onSuccessmap에서 각각 toDomain(today)를 호출하여 동일한 변환이 두 번 수행됩니다. 한 번만 변환하여 재사용하는 것이 효율적입니다.

♻️ 개선 제안
-            emotionRemoteDataSource.fetchDailyEmotion(today.toString())
-                .onSuccess { emotionLocalDataSource.saveDailyEmotion(it.toDomain(today)) }
-                .map { it.toDomain(today) }
+            emotionRemoteDataSource.fetchDailyEmotion(today.toString())
+                .map { it.toDomain(today) }
+                .onSuccess { emotionLocalDataSource.saveDailyEmotion(it) }
🤖 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/emotion/repositoryImpl/EmotionRepositoryImpl.kt`
around lines 79 - 81, The code calls toDomain(today) twice when handling
emotionRemoteDataSource.fetchDailyEmotion; refactor to compute the domain object
once, reuse it for saving and mapping. Specifically, inside the
fetchDailyEmotion chain (the call using onSuccess and map), transform the
fetched DTO to a single val domain = it.toDomain(today) (or use let/also) and
pass that same domain to emotionLocalDataSource.saveDailyEmotion(domain) and to
the downstream mapping instead of calling toDomain(today) again.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt`:
- Around line 79-81: The code calls toDomain(today) twice when handling
emotionRemoteDataSource.fetchDailyEmotion; refactor to compute the domain object
once, reuse it for saving and mapping. Specifically, inside the
fetchDailyEmotion chain (the call using onSuccess and map), transform the
fetched DTO to a single val domain = it.toDomain(today) (or use let/also) and
pass that same domain to emotionLocalDataSource.saveDailyEmotion(domain) and to
the downstream mapping instead of calling toDomain(today) again.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61e53630-9888-436b-bba4-caca5e28bc80

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4add3 and 12f5dec.

📒 Files selected for processing (1)
  • data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt

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] DailyEmotion 데이터 흐름을 StateFlow 기반 반응형 스트림으로 개선

2 participants