Skip to content

Commit 9d4add3

Browse files
committed
Refactor: Emotion 데이터 레이어 구조 개선 및 캐싱 로직 수정
1 parent 3edbfd1 commit 9d4add3

10 files changed

Lines changed: 109 additions & 36 deletions

File tree

app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import com.threegap.bitnagil.data.auth.datasource.AuthLocalDataSource
88
import com.threegap.bitnagil.data.auth.datasource.AuthRemoteDataSource
99
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthLocalDataSourceImpl
1010
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthRemoteDataSourceImpl
11-
import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource
12-
import com.threegap.bitnagil.data.emotion.datasourceImpl.EmotionDataSourceImpl
11+
import com.threegap.bitnagil.data.emotion.datasource.EmotionLocalDataSource
12+
import com.threegap.bitnagil.data.emotion.datasource.EmotionRemoteDataSource
13+
import com.threegap.bitnagil.data.emotion.datasourceImpl.EmotionLocalDataSourceImpl
14+
import com.threegap.bitnagil.data.emotion.datasourceImpl.EmotionRemoteDataSourceImpl
1315
import com.threegap.bitnagil.data.file.datasource.FileDataSource
1416
import com.threegap.bitnagil.data.file.datasourceImpl.FileDataSourceImpl
1517
import com.threegap.bitnagil.data.onboarding.datasource.OnBoardingDataSource
@@ -52,7 +54,11 @@ abstract class DataSourceModule {
5254

5355
@Binds
5456
@Singleton
55-
abstract fun bindEmotionDataSource(emotionDataSourceImpl: EmotionDataSourceImpl): EmotionDataSource
57+
abstract fun bindEmotionLocalDataSource(emotionLocalDataSourceImpl: EmotionLocalDataSourceImpl): EmotionLocalDataSource
58+
59+
@Binds
60+
@Singleton
61+
abstract fun bindEmotionRemoteDataSource(emotionRemoteDataSourceImpl: EmotionRemoteDataSourceImpl): EmotionRemoteDataSource
5662

5763
@Binds
5864
@Singleton
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.threegap.bitnagil.data.emotion.datasource
2+
3+
import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
4+
import kotlinx.coroutines.flow.StateFlow
5+
6+
interface EmotionLocalDataSource {
7+
val dailyEmotion: StateFlow<DailyEmotion?>
8+
fun saveDailyEmotion(dailyEmotion: DailyEmotion)
9+
fun clearCache()
10+
}

data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt renamed to data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionRemoteDataSource.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.threegap.bitnagil.data.emotion.model.dto.EmotionDto
44
import com.threegap.bitnagil.data.emotion.model.response.DailyEmotionResponse
55
import com.threegap.bitnagil.data.emotion.model.response.RegisterEmotionResponse
66

7-
interface EmotionDataSource {
7+
interface EmotionRemoteDataSource {
88
suspend fun getEmotions(): Result<List<EmotionDto>>
99
suspend fun registerEmotion(emotion: String): Result<RegisterEmotionResponse>
1010
suspend fun fetchDailyEmotion(currentDate: String): Result<DailyEmotionResponse>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.threegap.bitnagil.data.emotion.datasourceImpl
2+
3+
import com.threegap.bitnagil.data.emotion.datasource.EmotionLocalDataSource
4+
import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
5+
import kotlinx.coroutines.flow.MutableStateFlow
6+
import kotlinx.coroutines.flow.StateFlow
7+
import kotlinx.coroutines.flow.asStateFlow
8+
import kotlinx.coroutines.flow.update
9+
import javax.inject.Inject
10+
11+
class EmotionLocalDataSourceImpl @Inject constructor() : EmotionLocalDataSource {
12+
private val _dailyEmotion = MutableStateFlow<DailyEmotion?>(null)
13+
override val dailyEmotion: StateFlow<DailyEmotion?> = _dailyEmotion.asStateFlow()
14+
15+
override fun saveDailyEmotion(dailyEmotion: DailyEmotion) {
16+
_dailyEmotion.update { dailyEmotion }
17+
}
18+
19+
override fun clearCache() {
20+
_dailyEmotion.update { null }
21+
}
22+
}

data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt renamed to data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionRemoteDataSourceImpl.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package com.threegap.bitnagil.data.emotion.datasourceImpl
22

33
import com.threegap.bitnagil.data.common.safeApiCall
4-
import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource
4+
import com.threegap.bitnagil.data.emotion.datasource.EmotionRemoteDataSource
55
import com.threegap.bitnagil.data.emotion.model.dto.EmotionDto
66
import com.threegap.bitnagil.data.emotion.model.request.RegisterEmotionRequest
77
import com.threegap.bitnagil.data.emotion.model.response.DailyEmotionResponse
88
import com.threegap.bitnagil.data.emotion.model.response.RegisterEmotionResponse
99
import com.threegap.bitnagil.data.emotion.service.EmotionService
1010
import javax.inject.Inject
1111

12-
class EmotionDataSourceImpl @Inject constructor(
12+
class EmotionRemoteDataSourceImpl @Inject constructor(
1313
private val emotionService: EmotionService,
14-
) : EmotionDataSource {
14+
) : EmotionRemoteDataSource {
1515
override suspend fun getEmotions(): Result<List<EmotionDto>> {
1616
return safeApiCall {
1717
emotionService.getEmotions()
Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,84 @@
11
package com.threegap.bitnagil.data.emotion.repositoryImpl
22

3-
import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource
3+
import com.threegap.bitnagil.data.emotion.datasource.EmotionLocalDataSource
4+
import com.threegap.bitnagil.data.emotion.datasource.EmotionRemoteDataSource
45
import com.threegap.bitnagil.data.emotion.model.response.toDomain
56
import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
67
import com.threegap.bitnagil.domain.emotion.model.Emotion
78
import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine
89
import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository
910
import kotlinx.coroutines.flow.Flow
10-
import kotlinx.coroutines.flow.MutableStateFlow
11-
import kotlinx.coroutines.flow.onSubscription
11+
import kotlinx.coroutines.flow.emitAll
12+
import kotlinx.coroutines.flow.filterNotNull
13+
import kotlinx.coroutines.flow.flow
14+
import kotlinx.coroutines.flow.map
15+
import kotlinx.coroutines.sync.Mutex
16+
import kotlinx.coroutines.sync.withLock
1217
import java.time.LocalDate
13-
import java.util.concurrent.atomic.AtomicBoolean
1418
import javax.inject.Inject
1519

1620
class EmotionRepositoryImpl @Inject constructor(
17-
private val emotionDataSource: EmotionDataSource,
21+
private val emotionRemoteDataSource: EmotionRemoteDataSource,
22+
private val emotionLocalDateSource: EmotionLocalDataSource,
1823
) : EmotionRepository {
1924

20-
private val isFetching = AtomicBoolean(false)
21-
private val _dailyEmotionFlow = MutableStateFlow(DailyEmotion.INIT)
22-
override val dailyEmotionFlow: Flow<DailyEmotion> = _dailyEmotionFlow
23-
.onSubscription {
24-
if (_dailyEmotionFlow.value.isStale(LocalDate.now())) fetchDailyEmotion()
25-
}
25+
private val fetchMutex = Mutex()
2626

2727
override suspend fun getEmotions(): Result<List<Emotion>> {
28-
return emotionDataSource.getEmotions().map { response ->
28+
return emotionRemoteDataSource.getEmotions().map { response ->
2929
response.map { it.toDomain() }
3030
}
3131
}
3232

3333
override suspend fun registerEmotion(emotionMarbleType: String): Result<List<EmotionRecommendRoutine>> {
34-
return emotionDataSource.registerEmotion(emotionMarbleType).map {
34+
return emotionRemoteDataSource.registerEmotion(emotionMarbleType).map {
3535
it.recommendedRoutines.map { emotionRecommendedRoutineDto ->
3636
emotionRecommendedRoutineDto.toEmotionRecommendRoutine()
3737
}
3838
}.also {
39-
if (it.isSuccess) fetchDailyEmotion()
39+
if (it.isSuccess) fetchAndSaveDailyEmotion(today = LocalDate.now(), forceRefresh = true)
4040
}
4141
}
4242

43-
override suspend fun fetchDailyEmotion(): Result<Unit> {
44-
if (!isFetching.compareAndSet(false, true)) return Result.success(Unit)
45-
return try {
46-
val today = LocalDate.now()
47-
emotionDataSource.fetchDailyEmotion(today.toString()).map {
48-
_dailyEmotionFlow.value = it.toDomain(today)
43+
override fun observeDailyEmotion(): Flow<Result<DailyEmotion>> = flow {
44+
fetchAndSaveDailyEmotion(LocalDate.now())
45+
.onFailure {
46+
emit(Result.failure(it))
47+
return@flow
48+
}
49+
50+
emitAll(
51+
emotionLocalDateSource.dailyEmotion
52+
.filterNotNull()
53+
.map { Result.success(it) },
54+
)
55+
}
56+
57+
override fun clearCache() {
58+
emotionLocalDateSource.clearCache()
59+
}
60+
61+
private suspend fun fetchAndSaveDailyEmotion(
62+
today: LocalDate,
63+
forceRefresh: Boolean = false,
64+
): Result<DailyEmotion> {
65+
if (!forceRefresh) {
66+
val currentLocalData = emotionLocalDateSource.dailyEmotion.value
67+
if (currentLocalData != null && !currentLocalData.isStale(today)) {
68+
return Result.success(currentLocalData)
69+
}
70+
}
71+
72+
return fetchMutex.withLock {
73+
if (!forceRefresh) {
74+
val doubleCheckData = emotionLocalDateSource.dailyEmotion.value
75+
if (doubleCheckData != null && !doubleCheckData.isStale(today)) {
76+
return@withLock Result.success(doubleCheckData)
77+
}
4978
}
50-
} finally {
51-
isFetching.set(false)
79+
emotionRemoteDataSource.fetchDailyEmotion(today.toString())
80+
.onSuccess { emotionLocalDateSource.saveDailyEmotion(it.toDomain(today)) }
81+
.map { it.toDomain(today) }
5282
}
5383
}
5484
}

domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine
66
import kotlinx.coroutines.flow.Flow
77

88
interface EmotionRepository {
9-
val dailyEmotionFlow: Flow<DailyEmotion>
109
suspend fun getEmotions(): Result<List<Emotion>>
1110
suspend fun registerEmotion(emotionMarbleType: String): Result<List<EmotionRecommendRoutine>>
12-
suspend fun fetchDailyEmotion(): Result<Unit>
11+
fun observeDailyEmotion(): Flow<Result<DailyEmotion>>
12+
fun clearCache()
1313
}

domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/ObserveDailyEmotionUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import javax.inject.Inject
88
class ObserveDailyEmotionUseCase @Inject constructor(
99
private val emotionRepository: EmotionRepository,
1010
) {
11-
operator fun invoke(): Flow<DailyEmotion> = emotionRepository.dailyEmotionFlow
11+
operator fun invoke(): Flow<Result<DailyEmotion>> = emotionRepository.observeDailyEmotion()
1212
}

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/home/HomeViewModel.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,14 @@ class HomeViewModel @Inject constructor(
5959
private fun observeDailyEmotion() {
6060
intent {
6161
repeatOnSubscription {
62-
observeDailyEmotionUseCase()
63-
.map { it.toUiModel() }
64-
.collect { reduce { state.copy(dailyEmotion = it) } }
62+
observeDailyEmotionUseCase().collect { result ->
63+
result.fold(
64+
onSuccess = { dailyEmotion ->
65+
reduce { state.copy(dailyEmotion = dailyEmotion.toUiModel()) }
66+
},
67+
onFailure = {},
68+
)
69+
}
6570
}
6671
}
6772
}

presentation/src/main/java/com/threegap/bitnagil/presentation/screen/recommendroutine/RecommendRoutineViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class RecommendRoutineViewModel @Inject constructor(
3131
onCreate = {
3232
repeatOnSubscription {
3333
observeDailyEmotionUseCase()
34-
.map { it.type }
34+
.map { result -> result.getOrNull()?.type }
3535
.distinctUntilChanged()
3636
.collect { loadRecommendRoutines() }
3737
}

0 commit comments

Comments
 (0)