Skip to content

feat: 피드 화면#908

Open
devfeijoa wants to merge 21 commits into
developfrom
feat/891
Open

feat: 피드 화면#908
devfeijoa wants to merge 21 commits into
developfrom
feat/891

Conversation

@devfeijoa

@devfeijoa devfeijoa commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

📌𝘐𝘴𝘴𝘶𝘦𝘴

📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯

  • 아직 개발중입니다ㅠㅠ 리뷰하지 말아주세요~!

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴

Summary by CodeRabbit

릴리스 노트

  • Refactor
    • 좋아요 동기화 메커니즘 개선: 기존 방식에서 더 견고한 로컬 저장소 기반 방식으로 전환되었습니다. 이제 앱 재시작 후에도 동기화되지 않은 좋아요 상태가 안정적으로 유지됩니다.

devfeijoa added 17 commits June 22, 2026 15:07
- `CachedFeedLikeState.kt`: 피드의 좋아요 여부(`isLiked`)와 좋아요 수(`likeCount`)를 관리하는 `CachedFeedLikeState` internal 데이터 클래스 추가
- `feedDetailStates` (ConcurrentHashMap)를 추가하여 피드 상세 정보의 좋아요 상태(`isLiked`, `likeCount`)를 별도로 관리하도록 개선
- `saveLikeState` 시 메인 피드 리스트(Flow)에 해당 데이터가 없는 경우에도 상세 캐시 상태를 업데이트하도록 로직 수정
- `fetchFeed` 수행 시 서버에서 조회한 최신 좋아요 상태를 `feedDetailStates` 캐시에 반영하는 로직 추가
- 캐시된 피드 존재 여부를 확인하기 위한 `findCachedFeed` 메서드 및 상세 상태 업데이트를 위한 `updateFeedDetailState` 메서드 구현
- 미동기화된 상태를 의미하는 `dirty` 관련 명칭을 사용자 의도가 반영된 대기 상태를 의미하는 `pendingLike`로 변경
- `dirtyFeedStates`, `originalFeedStates`를 각각 `pendingLikeStates`, `originalLikeStates`로 변경
- `applyDirtyState`, `trackDirtyState` 등의 메서드를 `applyPendingLikeState`, `trackPendingLikeState`로 변경하여 가독성 개선
- `UpdatedFeedRepository.kt`: 메서드명을 `syncDirtyFeeds`에서 `syncPendingLikes`로 변경하고, 좋아요 상태를 동기화한다는 점을 명확히 하도록 주석 수정
- `UpdatedFeedDetailViewModel.kt`, `FeedViewModel.kt`: ViewModel이 해제되는 시점(`onCleared`)에 호출되는 메서드명을 변경된 명칭(`syncPendingLikes`)에 맞춰 수정
- 피드별 좋아요 상태(`isLiked`)를 관리하기 위한 `pendingLikes` Flow 및 CRUD 메서드 정의
- 서버 반영 전 로컬의 임시 상태를 관리하고, 상태 일치 시 삭제하는 기능을 포함하는 `PendingFeedLikeStore` 인터페이스 구현
- `DataStoreQualifier.kt`: 좋아요 요청 보류 상태를 관리하는 DataStore를 구분하기 위한 `@PendingFeedLikeDataStore` 한정자(Qualifier) 추가
- `DataStoreQualifier.kt`: 좋아요 요청 보류 상태를 관리하는 DataStore를 구분하기 위한 `@PendingFeedLikeDataStore` 한정자(Qualifier) 추가
- `PendingFeedLikesPreferences.kt`: 피드 ID(`feedId`)와 좋아요 여부(`isLiked`)를 저장하는 `PendingFeedLikesPreferences` 및 `PendingFeedLikePreferences` 데이터 클래스 정의
- `DefaultPendingFeedLikeStore`: DataStore를 사용하여 피드 ID별 좋아요 보류 상태(`isLiked`)를 관리하는 로직 구현
- `PendingFeedLikesPreferences` 모델을 JSON 문자열로 인코딩/디코딩하여 저장하는 직렬화 로직 추가
- 보류 중인 좋아요 업데이트(`updatePendingLike`) 및 상태 일치 시 삭제(`deletePendingLikeIfMatched`) 기능 구현
- `PendingFeedLikeStoreModule`: Hilt를 이용한 `PendingFeedLikeStore` 의존성 주입 설정 추가
- `DefaultPendingFeedLikeStore`: DataStore를 사용하여 피드 ID별 좋아요 보류 상태(`isLiked`)를 관리하는 로직 구현
- `PendingFeedLikesPreferences` 모델을 JSON 문자열로 인코딩/디코딩하여 저장하는 직렬화 로직 추가
- 보류 중인 좋아요 업데이트(`updatePendingLike`) 및 상태 일치 시 삭제(`deletePendingLikeIfMatched`) 기능 구현
- `PendingFeedLikeStoreModule`: Hilt를 이용한 `PendingFeedLikeStore` 의존성 주입 설정 추가
- `PendingFeedLikeStore`: 특정 피드 ID의 보류된 좋아요 상태를 삭제하는 `deletePendingLike` 인터페이스 메서드 추가
- `DefaultPendingFeedLikeStore`: `deletePendingLike` 구현을 통해 DataStore에서 특정 피드 ID의 데이터를 삭제하고, 전체 데이터가 비어있을 경우 키 자체를 제거하도록 로직 반영
- `UpdatedFeedRepository`에 `PendingFeedLikeStore`를 주입하여 앱 종료 후에도 좋아요 상태를 유지할 수 있도록 개선
- 좋아요 상태가 변경될 때 호출되는 `updatePendingLike` 및 `deletePendingLike` 메서드 구현
- 변경된 좋아요 상태가 원본 상태와 다를 경우 로컬에 저장하고, 원본과 일치할 경우 로컬 데이터를 삭제하도록 로직 수정
- `UpdatedFeedRepository`: 서버와 좋아요 상태 동기화 성공 시, `pendingFeedLikeStore`에 저장된 임시 데이터를 삭제하는 `deleteSyncedPendingLike` 로직 구현 및 적용
- `observePendingLikes` 메서드를 통해 `pendingFeedLikeStore`의 변경 사항을 구독하고, `pendingLikeStates`를 실시간으로 업데이트하도록 수정
- 펜딩된 좋아요 상태를 모든 피드 리스트(`_sosoAllFeeds`, `_sosoRecommendedFeeds`, `_myFeeds`)에 즉각 반영하는 `applyPendingLikeStatesToCachedFeeds` 로직 구현
- `syncPendingLikes`: 동기화 시 메모리(`pendingLikeStates`)뿐만 아니라 로컬 저장소(`pendingFeedLikeStore`)의 데이터도 함께 처리하도록 수정
- `deleteSyncedPendingLike`: 동기화 완료 후 데이터를 삭제하는 로직을 메모리(`FromMemory`)와 로컬 저장소(`FromStore`)용으로 분리하여 관리 최적화
- 동기화 시작 전 빈 맵 확인 로직을 코루틴 내부로 이동하여 데이터 유실 방지 및 안정성 향상
- `UpdatedFeedRepository`: `pendingFeedLikeStore`에 대한 쓰기 작업의 순서 보장을 위해 `enqueuePendingLikeStoreWrite` 메서드 구현 및 `Job.join()`을 이용한 직렬화 로직 적용
- 초기화 시 `Flow.collect`를 통한 관찰 방식 대신 `getPendingLikes`를 호출하여 일회성으로 데이터를 복구하도록 `restorePendingLikes` 로직 수정
- `savePendingLike` 및 `deletePendingLike` 호출 시 새로운 쓰기 큐 로직을 적용하여 데이터 정합성 강화
- `pendingLikeStoreWriteLock` (Any) 및 `Job`을 이용한 순차 처리 로직을 `Mutex`와 `withLock`을 사용하는 방식으로 변경하여 코루틴 환경에서의 안정성 및 가독성 향상
- `enqueuePendingLikeStoreWrite` 메서드를 `writePendingLikeStore`로 네이밍 변경 및 로직 최적화
@devfeijoa devfeijoa requested review from Sadturtleman, m6z1 and s9hn June 23, 2026 08:43
@devfeijoa devfeijoa self-assigned this Jun 23, 2026
@devfeijoa devfeijoa added 🍯 [FEAT] 새로운 기능을 개발합니다. [👸 공주 은영] labels Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

피드 좋아요 미동기화 상태 관리를 dirty 기반 인메모리 방식에서 DataStore 기반 pendingLikes 방식으로 전환했습니다. PendingFeedLikeStore 인터페이스와 DataStore 구현체를 신규 추가하고, UpdatedFeedRepository의 토글 추적·동기화 로직을 전면 교체하며, ViewModel callsites를 업데이트했습니다.

Changes

Pending Likes 전환

Layer / File(s) Summary
PendingFeedLikeStore 인터페이스 및 캐시 모델 정의
data/feed/.../store/PendingFeedLikeStore.kt, data/feed/.../repository/model/CachedFeedLikeState.kt
PendingFeedLikeStore 인터페이스(Flow<Map<Long, Boolean>> 프로퍼티 + suspend CRUD 계약)와 CachedFeedLikeState 내부 데이터 클래스를 신규 추가해 pending 좋아요 상태의 공개 계약을 확립합니다.
DataStore 인프라: 직렬화 모델·DI·DefaultPendingFeedLikeStore 구현
core/datastore/.../model/PendingFeedLikesPreferences.kt, core/datastore/.../di/DataStoreQualifier.kt, core/datastore/.../di/DataStoreModule.kt, core/datastore/.../datasource/feed/DefaultPendingFeedLikeStore.kt, core/datastore/build.gradle.kts
@Serializable preferences 모델 2종, PendingFeedLikeDataStore qualifier, DataStore singleton 제공 함수, DefaultPendingFeedLikeStore 전체 구현(Flow 노출·JSON encode/decode·조건부 삭제), Hilt @Binds 모듈을 추가하고 data.feed 모듈 의존성을 빌드 파일에 등록합니다.
UpdatedFeedRepository: dirty → pending 전면 교체
data/feed/.../repository/UpdatedFeedRepository.kt
생성자에 PendingFeedLikeStore를 주입하고, init에서 스토어 상태를 메모리로 복원합니다. applyDirtyState 계열을 applyPendingLikeState 계열로 교체해 목록·상세·캐시 Flow 병합에 통일 적용하며, syncDirtyFeeds/trackDirtyStatesyncPendingLikes/trackPendingLikeState(mutex+예외 로그, store+memory 합산 후 API 호출 및 성공 시 삭제)로 대체합니다.
ViewModel onCleared() callsites 업데이트
app/.../ui/feedDetail/UpdatedFeedDetailViewModel.kt, feature/feed/.../FeedViewModel.kt
onCleared()에서 feedRepository.syncDirtyFeeds() 호출을 feedRepository.syncPendingLikes()로 교체합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • s9hn
  • m6z1

Poem

🐰 토끼가 발을 구르며 노래합니다:

dirty 메모는 이제 안녕, pending이 왔네 🗃️
DataStore 깊은 곳에 좋아요를 꼭꼭 저장해
mutex 자물쇠로 동시성도 잠그고
앱이 꺼져도 잊지 않을 거야 🔒
pending likes, 영원히 기억할게! 🐾

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning 템플릿의 주요 섹션은 있으나 Work Description이 개발 중 안내만 있고 실제 변경 내용이 없어 설명이 크게 부족합니다. 변경 목적, 핵심 구현 내용, 영향 범위, 테스트/검증 결과를 Work Description에 구체적으로 추가하고, 스크린샷과 리뷰 요청 사항도 채워주세요.
Title check ❓ Inconclusive PR 제목이 매우 일반적이고 구체성이 부족합니다. '피드 화면'은 변경 사항의 핵심을 명확하게 전달하지 못합니다. pending like 상태 캐싱 및 동기화 기능 구현 등 구체적인 변경 내용을 반영하여 제목을 보다 명확하게 수정하시기 바랍니다.
Linked Issues check ❓ Inconclusive 연결된 issue #891에 구체적인 요구사항, 수락 조건, 또는 명확한 스코프 정의가 없어 코드 변경이 요구사항을 충족하는지 검증하기 어렵습니다. issue #891에 구체적인 요구사항 및 수락 조건을 명시하거나, 필요시 자식 issue로 분리하여 각 기능별 요구사항을 명확히 정의해주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed 모든 코드 변경이 pending like 상태 캐싱 및 동기화 구현이라는 일관된 목표와 관련이 있으며, 범위를 벗어난 변경은 없어 보입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 feat/891

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
data/feed/src/main/java/com/into/websoso/data/feed/store/PendingFeedLikeStore.kt (1)

17-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

deletePendingLikeIfMatched의 Boolean 반환값에 대한 문서화를 고려해보세요.

메서드의 Boolean 반환값이 무엇을 의미하는지(매칭 성공 여부인지, 삭제 성공 여부인지) KDoc으로 명시하면 구현체와 호출자 모두에게 도움이 될 것입니다.

📝 KDoc 추가 예시
+    /**
+     * 저장된 좋아요 상태가 [isLiked]와 일치하는 경우에만 삭제합니다.
+     * 
+     * `@return` 매칭되어 삭제된 경우 true, 일치하지 않아 삭제하지 않은 경우 false
+     */
     suspend fun deletePendingLikeIfMatched(
         feedId: Long,
         isLiked: Boolean,
     ): Boolean
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@data/feed/src/main/java/com/into/websoso/data/feed/store/PendingFeedLikeStore.kt`
around lines 17 - 20, The method deletePendingLikeIfMatched in
PendingFeedLikeStore returns a Boolean but lacks documentation explaining what
this return value represents (whether it indicates matching success, deletion
success, or other states). Add a KDoc comment above the method that clearly
documents the return value, specifying what the Boolean return type means in the
context of this operation to help both implementers and callers understand the
semantics.
core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/feed/DefaultPendingFeedLikeStore.kt (1)

47-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Kotlin 인덱싱 구문 선호

Line 51에서 pendingLikes.put(feedId, isLiked) 대신 Kotlin 관용적 인덱싱 구문 pendingLikes[feedId] = isLiked를 사용하는 것이 더 idiomatic합니다.

♻️ 제안 수정
                val pendingLikes: MutableMap<Long, Boolean> = decodePendingLikes(
                    preferences[PENDING_FEED_LIKES_KEY],
                ).toMutableMap()
-               pendingLikes.put(feedId, isLiked)
+               pendingLikes[feedId] = isLiked
                preferences[PENDING_FEED_LIKES_KEY] = encodePendingLikes(pendingLikes)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/feed/DefaultPendingFeedLikeStore.kt`
around lines 47 - 53, In the DefaultPendingFeedLikeStore.kt file within the edit
lambda of pendingFeedLikeDataStore, replace the Java-style map method call
pendingLikes.put(feedId, isLiked) with the more idiomatic Kotlin indexing syntax
pendingLikes[feedId] = isLiked. This makes the code more consistent with Kotlin
conventions and improves readability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@data/feed/src/main/java/com/into/websoso/data/feed/repository/UpdatedFeedRepository.kt`:
- Around line 371-387: The syncPendingLikes() method has a race condition where
a snapshot of pending likes is read at the start, but if the user toggles the
same feed during synchronization in another ViewModel, the pending state changes
while the API call is still in flight, causing server and UI to become out of
sync. Fix this by re-checking the current pending state before deleting after
successful API completion in the onSuccess block of the runCatching in
syncPendingLikes(). Before calling deleteSyncedPendingLikeFromStore(id, isLiked)
and deleteSyncedPendingLikeFromMemory(id, isLiked), verify that the current
pending state still matches the original isLiked value from the snapshot, and
only proceed with deletion if it matches. If the state has changed, skip the
deletion and let the next sync cycle handle it.
- Around line 61-77: The restorePendingLikes() method only restores
pendingLikeStates but not originalLikeStates, causing state mismatch after app
restart. Modify the restorePendingLikes() function to also retrieve and restore
the originalLikeStates from the pendingFeedLikeStore alongside
pendingLikeStates. Update the pendingFeedLikeStore interface to include methods
that save and retrieve both pendingLikeStates and originalLikeStates together,
ensuring that when pending likes are restored after app restart, the original
server state is also available for comparison in trackPendingLikeState() and
related state management methods.

---

Nitpick comments:
In
`@core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/feed/DefaultPendingFeedLikeStore.kt`:
- Around line 47-53: In the DefaultPendingFeedLikeStore.kt file within the edit
lambda of pendingFeedLikeDataStore, replace the Java-style map method call
pendingLikes.put(feedId, isLiked) with the more idiomatic Kotlin indexing syntax
pendingLikes[feedId] = isLiked. This makes the code more consistent with Kotlin
conventions and improves readability.

In
`@data/feed/src/main/java/com/into/websoso/data/feed/store/PendingFeedLikeStore.kt`:
- Around line 17-20: The method deletePendingLikeIfMatched in
PendingFeedLikeStore returns a Boolean but lacks documentation explaining what
this return value represents (whether it indicates matching success, deletion
success, or other states). Add a KDoc comment above the method that clearly
documents the return value, specifying what the Boolean return type means in the
context of this operation to help both implementers and callers understand the
semantics.
🪄 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: 6ba614bc-68cd-4ac2-a486-6c2c0b9bdcec

📥 Commits

Reviewing files that changed from the base of the PR and between e9f6cb4 and 6ae731a.

📒 Files selected for processing (10)
  • app/src/main/java/com/into/websoso/ui/feedDetail/UpdatedFeedDetailViewModel.kt
  • core/datastore/build.gradle.kts
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/feed/DefaultPendingFeedLikeStore.kt
  • core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/feed/model/PendingFeedLikesPreferences.kt
  • core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt
  • core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt
  • data/feed/src/main/java/com/into/websoso/data/feed/repository/UpdatedFeedRepository.kt
  • data/feed/src/main/java/com/into/websoso/data/feed/repository/model/CachedFeedLikeState.kt
  • data/feed/src/main/java/com/into/websoso/data/feed/store/PendingFeedLikeStore.kt
  • feature/feed/src/main/java/com/into/websoso/feature/feed/FeedViewModel.kt

- 이미 나간 sync 요청이 성공했을 때 최신 로컬 상태와 다시 맞춰보는 보정 로직
- `UpdatedFeedRepository.kt`: 좋아요 동기화 성공 시 현재 상태를 재확인하여 처리하는 `handleSyncedPendingLike` 메서드 추가
- 동기화 중 사용자가 좋아요 상태를 변경한 경우, 해당 변경 사항이 유실되지 않도록 `pendingLikeStates`를 유지하고 업데이트하는 로직 적용
- 피드 캐시 또는 상세 상태에서 현재 좋아요 상태를 조회하는 `findCurrentLikeState` 메서드 추가로 데이터 정합성 강화
- 앱 재시작 후 pending 병합 시,
  서버 원본 isLiked를 originalLikeStates에 기록해서 pending 제거 판단 기준을 복구

- `UpdatedFeedRepository` 내 `applyPendingLikeState` 및 `applyPendingLikeStateToDetail` 함수에서 좋아요 상태 변경 전 원본 값을 `originalLikeStates`에 저장하도록 수정하여 데이터 일관성 보장 기반 마련
- `DefaultPendingFeedLikeStore.kt`: `pendingLikes.put(feedId, isLiked)` 호출 방식을 코틀린 관용구인 인덱스 연산자(`[]`) 사용으로 변경
- `PendingFeedLikeStore.kt`: `deletePendingLikeIfMatched` 메서드에 파라미터 설명 및 반환값 정의를 포함한 KDoc 주석 추가

@Sadturtleman Sadturtleman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다!

@Sadturtleman Sadturtleman self-requested a review July 1, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[👸 공주 은영] 🍯 [FEAT] 새로운 기능을 개발합니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 피드 화면

2 participants