Skip to content

Commit 3203968

Browse files
authored
Merge pull request #88 from YAPP-Github/refactor/#85-random-pose-call-site
[refactor] #85 랜덤포즈 중복 확인 위치 변경
2 parents 5387683 + 0403489 commit 3203968

6 files changed

Lines changed: 80 additions & 80 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.neki.android.core.common.exception
2+
3+
class RandomPoseRetryExhaustedException(
4+
message: String,
5+
) : RuntimeException(message)

core/data-api/src/main/java/com/neki/android/core/dataapi/repository/PoseRepository.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@ interface PoseRepository {
1919

2020
suspend fun getPose(poseId: Long): Result<Pose>
2121

22-
suspend fun getRandomPose(headCount: PeopleCount): Result<Pose>
22+
suspend fun getSingleRandomPose(
23+
headCount: PeopleCount,
24+
excludeIds: Set<Long>,
25+
maxRetry: Int,
26+
): Result<Pose>
27+
28+
suspend fun getMultipleRandomPose(
29+
headCount: PeopleCount,
30+
excludeIds: Set<Long>,
31+
poseSize: Int,
32+
maxRetry: Int,
33+
): Result<List<Pose>>
2334

2435
suspend fun updateScrap(poseId: Long, scrap: Boolean): Result<Unit>
2536
}

core/data/src/main/java/com/neki/android/core/data/repository/impl/PoseRepositoryImpl.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.neki.android.core.data.repository.impl
33
import androidx.paging.Pager
44
import androidx.paging.PagingConfig
55
import androidx.paging.PagingData
6+
import com.neki.android.core.common.exception.RandomPoseRetryExhaustedException
67
import com.neki.android.core.data.paging.PosePagingSource
78
import com.neki.android.core.data.paging.ScrapPosePagingSource
89
import com.neki.android.core.data.remote.api.PoseService
@@ -65,8 +66,41 @@ class PoseRepositoryImpl @Inject constructor(
6566
poseService.getPose(poseId).data.toModel()
6667
}
6768

68-
override suspend fun getRandomPose(headCount: PeopleCount): Result<Pose> = runSuspendCatching {
69-
poseService.getRandomPose(headCount = headCount.name).data.toModel()
69+
override suspend fun getSingleRandomPose(
70+
headCount: PeopleCount,
71+
excludeIds: Set<Long>,
72+
maxRetry: Int,
73+
): Result<Pose> = runSuspendCatching {
74+
repeat(maxRetry) {
75+
val pose = poseService.getRandomPose(headCount = headCount.name).data.toModel()
76+
if (pose.id !in excludeIds) {
77+
return@runSuspendCatching pose
78+
}
79+
}
80+
throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요")
81+
}
82+
83+
override suspend fun getMultipleRandomPose(
84+
headCount: PeopleCount,
85+
excludeIds: Set<Long>,
86+
poseSize: Int,
87+
maxRetry: Int,
88+
): Result<List<Pose>> = runSuspendCatching {
89+
val result = mutableListOf<Pose>()
90+
val collectedIds = excludeIds.toMutableSet()
91+
var retryCount = 0
92+
93+
while (result.size < poseSize && retryCount < maxRetry) {
94+
val pose = poseService.getRandomPose(headCount = headCount.name).data.toModel()
95+
if (pose.id !in collectedIds) {
96+
result.add(pose)
97+
collectedIds.add(pose.id)
98+
} else {
99+
retryCount++
100+
}
101+
}
102+
103+
result.ifEmpty { throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요") }
70104
}
71105

72106
override suspend fun updateScrap(poseId: Long, scrap: Boolean): Result<Unit> = runSuspendCatching {

feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/const/PoseConst.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.neki.android.feature.pose.impl.const
33
internal object PoseConst {
44
internal const val INITIAL_POSE_LOAD_COUNT = 4
55
internal const val POSE_PREFETCH_THRESHOLD = 3
6-
internal const val MAXIMUM_RANDOM_POSE_FALLBACK_COUNT = 7
6+
internal const val MAXIMUM_RANDOM_POSE_RETRY_COUNT = 7
77

88
internal const val POSE_LAYOUT_DEFAULT_TOP_PADDING = 12
99
internal const val POSE_LAYOUT_BOTTOM_PADDING = 28

feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseContract.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ data class RandomPoseUiState(
1616

1717
val hasPrevious: Boolean
1818
get() = currentIndex > 0
19-
}
2019

21-
internal sealed class FetchPoseResult(val tryCount: Int) {
22-
class Success(tryCount: Int, val pose: Pose) : FetchPoseResult(tryCount)
23-
class Duplicated(tryCount: Int) : FetchPoseResult(tryCount)
24-
class Failure(tryCount: Int, val throwable: Throwable) : FetchPoseResult(tryCount)
20+
val randomPoseIds: Set<Long>
21+
get() = poseList.map { it.id }.toSet()
2522
}
2623

2724
sealed interface RandomPoseIntent {

feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/RandomPoseViewModel.kt

Lines changed: 24 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package com.neki.android.feature.pose.impl.random
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.neki.android.core.common.coroutine.di.ApplicationScope
6+
import com.neki.android.core.common.exception.RandomPoseRetryExhaustedException
67
import com.neki.android.core.dataapi.repository.PoseRepository
78
import com.neki.android.core.model.PeopleCount
8-
import com.neki.android.core.model.Pose
99
import com.neki.android.core.ui.MviIntentStore
1010
import com.neki.android.core.ui.mviIntentStore
1111
import com.neki.android.feature.pose.impl.const.PoseConst
@@ -152,21 +152,16 @@ internal class RandomPoseViewModel @AssistedInject constructor(
152152
// 여분 포즈가 POSE_PREFETCH_THRESHOLD 이하이면 다음 포즈 미리 캐싱
153153
if (state.poseList.lastIndex - nextIndex < PoseConst.POSE_PREFETCH_THRESHOLD) {
154154
viewModelScope.launch {
155-
when (val result = fetchRandomPose(poseList = state.poseList)) {
156-
is FetchPoseResult.Success -> reduce {
157-
copy(
158-
poseList = (poseList + result.pose).toImmutableList(),
159-
committedScraps = committedScraps + (result.pose.id to result.pose.isScrapped),
160-
)
161-
}
162-
163-
is FetchPoseResult.Duplicated ->
164-
Timber.d("프리페치 생략: ${result.tryCount}회 시도 후 중복 포즈")
165-
166-
is FetchPoseResult.Failure -> {
167-
Timber.e(result.throwable)
168-
postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요"))
169-
}
155+
poseRepository.getSingleRandomPose(
156+
headCount = peopleCount,
157+
excludeIds = state.randomPoseIds,
158+
maxRetry = PoseConst.MAXIMUM_RANDOM_POSE_RETRY_COUNT,
159+
).onSuccess { pose ->
160+
reduce { copy(poseList = (poseList + pose).toImmutableList()) }
161+
}.onFailure { error ->
162+
if (error is RandomPoseRetryExhaustedException)
163+
Timber.e(error, "중복 포즈")
164+
else Timber.e(error)
170165
}
171166
}
172167
}
@@ -179,73 +174,31 @@ internal class RandomPoseViewModel @AssistedInject constructor(
179174
viewModelScope.launch {
180175
reduce { copy(isLoading = true) }
181176

182-
val poses = mutableListOf<Pose>()
183-
var totalFallbackCount = 0
184-
185177
// 초기에 INITIAL_POSE_LOAD_COUNT개 로드
186-
while (
187-
poses.size < PoseConst.INITIAL_POSE_LOAD_COUNT &&
188-
totalFallbackCount < PoseConst.MAXIMUM_RANDOM_POSE_FALLBACK_COUNT
189-
) {
190-
val result = fetchRandomPose(
191-
poseList = poses,
192-
maxFallbackCount = PoseConst.MAXIMUM_RANDOM_POSE_FALLBACK_COUNT - totalFallbackCount,
193-
)
194-
195-
totalFallbackCount += result.tryCount
196-
197-
when (result) {
198-
is FetchPoseResult.Success -> poses.add(result.pose)
199-
is FetchPoseResult.Failure -> {
200-
Timber.e(result.throwable)
201-
postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요"))
202-
break
203-
}
204-
205-
is FetchPoseResult.Duplicated ->
206-
Timber.d("초기 로드: ${result.tryCount}회 시도 후 중복 포즈")
207-
}
208-
}
209-
210-
if (poses.isNotEmpty()) {
178+
poseRepository.getMultipleRandomPose(
179+
headCount = peopleCount,
180+
excludeIds = emptySet(),
181+
poseSize = PoseConst.INITIAL_POSE_LOAD_COUNT,
182+
maxRetry = PoseConst.MAXIMUM_RANDOM_POSE_RETRY_COUNT,
183+
).onSuccess { data ->
211184
reduce {
212185
copy(
213186
isLoading = false,
214-
poseList = poses.toImmutableList(),
215-
committedScraps = poses.associate { it.id to it.isScrapped },
187+
poseList = data.toImmutableList(),
188+
committedScraps = data.associate { it.id to it.isScrapped },
216189
currentIndex = 0,
217190
)
218191
}
219-
} else {
192+
}.onFailure { error ->
193+
Timber.e(error)
220194
reduce { copy(isLoading = false) }
221-
postSideEffect(RandomPoseEffect.ShowToast("포즈를 불러오는데 실패했어요"))
195+
if (error is RandomPoseRetryExhaustedException)
196+
Timber.e(error, "중복 포즈")
197+
else Timber.e(error)
222198
}
223199
}
224200
}
225201

226-
private suspend fun fetchRandomPose(
227-
poseList: List<Pose>,
228-
maxFallbackCount: Int = PoseConst.MAXIMUM_RANDOM_POSE_FALLBACK_COUNT,
229-
): FetchPoseResult {
230-
var tryCount = 0
231-
232-
while (tryCount < maxFallbackCount) {
233-
tryCount++
234-
poseRepository.getRandomPose(headCount = peopleCount)
235-
.onSuccess { pose ->
236-
if (poseList.none { it.id == pose.id }) {
237-
return FetchPoseResult.Success(tryCount, pose)
238-
}
239-
}
240-
.onFailure { error ->
241-
Timber.e(error)
242-
return FetchPoseResult.Failure(tryCount, error)
243-
}
244-
}
245-
246-
return FetchPoseResult.Duplicated(tryCount)
247-
}
248-
249202
override fun onCleared() {
250203
super.onCleared()
251204

0 commit comments

Comments
 (0)