Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
13f3fc4
feat: 피드 좋아요 상태 캐싱을 위한 데이터 모델 추가
devfeijoa Jun 22, 2026
6df2b10
fix: 피드 상세 좋아요 상태 동기화 및 캐싱 로직 추가
devfeijoa Jun 22, 2026
3198b31
fix: UpdatedFeedRepository 내 좋아요 상태 관리 변수 및 메서드 명칭 수정
devfeijoa Jun 22, 2026
77a2704
refactor: 피드 좋아요 상태 동기화 메서드 명칭 수정
devfeijoa Jun 22, 2026
efbaa6e
feat: 피드 좋아요 보류 상태 저장을 위한 `PendingFeedLikeStore` 인터페이스 추가
devfeijoa Jun 22, 2026
ecd3b39
feat: PendingFeedLikeDataStore를 위한 Dagger Qualifier 추가
devfeijoa Jun 22, 2026
92956a8
feat: PendingFeedLikeDataStore를 위한 Dagger Qualifier 추가
devfeijoa Jun 22, 2026
88fcbd5
feat: 피드 좋아요 보류 상태 저장을 위한 데이터 모델 추가
devfeijoa Jun 22, 2026
d324628
feat: 피드 좋아요 보류 상태 저장을 위한 DataStore 구현
devfeijoa Jun 22, 2026
7f2ef93
feat: 피드 좋아요 보류 상태 저장을 위한 DataStore 구현
devfeijoa Jun 22, 2026
47ffc95
feat: 피드 좋아요 보류 상태 삭제 기능 추가
devfeijoa Jun 22, 2026
65b4e63
feat: 좋아요 상태 변경 시 로컬 저장소 동기화 로직 추가
devfeijoa Jun 22, 2026
e90aceb
fix: 동기화 완료된 피드 좋아요 상태의 DataStore 삭제 로직 추가
devfeijoa Jun 22, 2026
808fd0a
fix: UpdatedFeedRepository 내 펜딩된 좋아요 상태 동기화 로직 추가
devfeijoa Jun 22, 2026
9bd1122
fix: 좋아요 상태 동기화 로직 보완 및 메모리/로컬 저장소 관리 개선
devfeijoa Jun 22, 2026
40a43ea
fix: 피드 좋아요 상태 저장 로직의 순차 처리 보장 및 초기화 방식 수정
devfeijoa Jun 22, 2026
6ae731a
fix: `UpdatedFeedRepository` 내 좋아요 상태 저장 로직의 동시성 제어 방식 수정
devfeijoa Jun 23, 2026
d77e967
refactor: 피드 좋아요 동기화 로직 개선 및 예외 케이스 처리
devfeijoa Jun 24, 2026
6d8fa9d
fix: 피드 좋아요 상태의 원본 값 캐싱 로직 추가
devfeijoa Jun 24, 2026
faaa6e7
refactor: DefaultPendingFeedLikeStore 내 MutableMap 저장 로직 수정
devfeijoa Jun 24, 2026
407e534
docs: PendingFeedLikeStore 내 좋아요 상태 일치 여부에 따른 삭제 메서드 주석 추가
devfeijoa Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class UpdatedFeedDetailViewModel
*/
override fun onCleared() {
super.onCleared()
feedRepository.syncDirtyFeeds()
feedRepository.syncPendingLikes()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ android {
dependencies {
implementation(projects.core.common)
implementation(projects.data.account)
implementation(projects.data.feed)
implementation(projects.data.library)
implementation(projects.data.user)

implementation(libs.datastore.preferences)
implementation(libs.serialization.json)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.into.websoso.core.datastore.datasource.feed

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.into.websoso.core.common.dispatchers.Dispatcher
import com.into.websoso.core.common.dispatchers.WebsosoDispatchers
import com.into.websoso.core.datastore.datasource.feed.model.PendingFeedLikePreferences
import com.into.websoso.core.datastore.datasource.feed.model.PendingFeedLikesPreferences
import com.into.websoso.core.datastore.di.PendingFeedLikeDataStore
import com.into.websoso.data.feed.store.PendingFeedLikeStore
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import javax.inject.Inject
import javax.inject.Singleton

internal class DefaultPendingFeedLikeStore
@Inject
constructor(
@param:PendingFeedLikeDataStore private val pendingFeedLikeDataStore: DataStore<Preferences>,
@param:Dispatcher(WebsosoDispatchers.DEFAULT) private val dispatcher: CoroutineDispatcher,
) : PendingFeedLikeStore {
override val pendingLikes: Flow<Map<Long, Boolean>>
get() = pendingFeedLikeDataStore.data
.map { preferences ->
withContext(dispatcher) {
decodePendingLikes(preferences[PENDING_FEED_LIKES_KEY])
}
}.distinctUntilChanged()

override suspend fun getPendingLikes(): Map<Long, Boolean> = pendingLikes.first()

override suspend fun updatePendingLike(
feedId: Long,
isLiked: Boolean,
) {
pendingFeedLikeDataStore.edit { preferences ->
val pendingLikes: MutableMap<Long, Boolean> = decodePendingLikes(
preferences[PENDING_FEED_LIKES_KEY],
).toMutableMap()
pendingLikes[feedId] = isLiked
preferences[PENDING_FEED_LIKES_KEY] = encodePendingLikes(pendingLikes)
}
}

override suspend fun deletePendingLike(feedId: Long) {
pendingFeedLikeDataStore.edit { preferences ->
val pendingLikes: MutableMap<Long, Boolean> = decodePendingLikes(
preferences[PENDING_FEED_LIKES_KEY],
).toMutableMap()

pendingLikes.remove(feedId)
if (pendingLikes.isEmpty()) {
preferences.remove(PENDING_FEED_LIKES_KEY)
} else {
preferences[PENDING_FEED_LIKES_KEY] = encodePendingLikes(pendingLikes)
}
}
}

override suspend fun deletePendingLikeIfMatched(
feedId: Long,
isLiked: Boolean,
): Boolean {
var deleted = false

pendingFeedLikeDataStore.edit { preferences ->
val pendingLikes: MutableMap<Long, Boolean> = decodePendingLikes(
preferences[PENDING_FEED_LIKES_KEY],
).toMutableMap()
if (pendingLikes[feedId] != isLiked) return@edit

pendingLikes.remove(feedId)
if (pendingLikes.isEmpty()) {
preferences.remove(PENDING_FEED_LIKES_KEY)
} else {
preferences[PENDING_FEED_LIKES_KEY] = encodePendingLikes(pendingLikes)
}
deleted = true
}

return deleted
}

private fun decodePendingLikes(jsonString: String?): Map<Long, Boolean> {
if (jsonString == null) return emptyMap()

return runCatching {
Json
.decodeFromString<PendingFeedLikesPreferences>(jsonString)
.likes
.associate { pendingLike -> pendingLike.feedId to pendingLike.isLiked }
}.getOrDefault(emptyMap())
}

private fun encodePendingLikes(pendingLikes: Map<Long, Boolean>): String =
Json.encodeToString(
PendingFeedLikesPreferences(
likes = pendingLikes.entries.map { pendingLike ->
PendingFeedLikePreferences(
feedId = pendingLike.key,
isLiked = pendingLike.value,
)
},
),
)

companion object {
private val PENDING_FEED_LIKES_KEY = stringPreferencesKey("PENDING_FEED_LIKES_KEY")
}
}

@Module
@InstallIn(SingletonComponent::class)
internal interface PendingFeedLikeStoreModule {
@Binds
@Singleton
fun bindPendingFeedLikeStore(defaultPendingFeedLikeStore: DefaultPendingFeedLikeStore): PendingFeedLikeStore
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.into.websoso.core.datastore.datasource.feed.model

import kotlinx.serialization.Serializable

@Serializable
internal data class PendingFeedLikesPreferences(
val likes: List<PendingFeedLikePreferences>,
)

@Serializable
internal data class PendingFeedLikePreferences(
val feedId: Long,
val isLiked: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ internal object DataStoreModule {
name = USER_DATASTORE,
)

private const val PENDING_FEED_LIKE_DATASTORE = "PENDING_FEED_LIKE_DATASTORE"
private val Context.pendingFeedLikeDataStore: DataStore<Preferences> by preferencesDataStore(
name = PENDING_FEED_LIKE_DATASTORE,
)

@Provides
@Singleton
@AccountDataStore
Expand All @@ -47,4 +52,11 @@ internal object DataStoreModule {
fun provideUserDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.userDataStore

@Provides
@Singleton
@PendingFeedLikeDataStore
fun providePendingFeedLikeDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.pendingFeedLikeDataStore
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ internal annotation class LibraryFilterDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class UserDataStore

@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class PendingFeedLikeDataStore
Loading
Loading