Skip to content

Commit 4edb9e0

Browse files
authored
Merge pull request #93 from YAPP-Github/fix/#89-random-pose
[fix] #89: 랜덤 포즈 화면 개선
2 parents b5586ab + a853151 commit 4edb9e0

13 files changed

Lines changed: 191 additions & 87 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.neki.android.core.common.exception
2+
3+
class ServerApiException(
4+
val code: Int,
5+
message: String,
6+
) : Throwable(message)
7+
8+
class ClientApiException(
9+
val code: Int,
10+
message: String,
11+
) : Throwable(message)

core/common/src/main/java/com/neki/android/core/common/exception/RandomPoseRetryExhaustedException.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import com.neki.android.core.model.Pose
66
import com.neki.android.core.model.SortOrder
77
import kotlinx.coroutines.flow.Flow
88

9+
const val NO_MORE_RANDOM_POSE = 400
10+
911
interface PoseRepository {
1012

1113
fun getPosesFlow(
@@ -22,14 +24,12 @@ interface PoseRepository {
2224
suspend fun getSingleRandomPose(
2325
headCount: PeopleCount,
2426
excludeIds: Set<Long>,
25-
maxRetry: Int,
2627
): Result<Pose>
2728

2829
suspend fun getMultipleRandomPose(
2930
headCount: PeopleCount,
3031
excludeIds: Set<Long>,
3132
poseSize: Int,
32-
maxRetry: Int,
3333
): Result<List<Pose>>
3434

3535
suspend fun updateScrap(poseId: Long, scrap: Boolean): Result<Unit>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.neki.android.core.dataapi.repository
22

33
import com.neki.android.core.model.UserInfo
4+
import kotlinx.coroutines.flow.Flow
45

56
interface UserRepository {
7+
val hasVisitedRandomPose: Flow<Boolean>
8+
suspend fun setRandomPoseVisited()
69
suspend fun getUserInfo(): Result<UserInfo>
710
suspend fun updateUserInfo(nickname: String): Result<Unit>
11+
812
suspend fun updateProfileImage(mediaId: Long?): Result<Unit>
913
}

core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ private val Context.authDataStore: DataStore<Preferences> by preferencesDataStor
1717
private const val TOKEN_DATASTORE = "token_datastore"
1818
private val Context.tokenDataStore: DataStore<Preferences> by preferencesDataStore(name = TOKEN_DATASTORE)
1919

20+
private const val USER_DATASTORE = "user_datastore"
21+
private val Context.userDataStore: DataStore<Preferences> by preferencesDataStore(name = USER_DATASTORE)
22+
2023
@InstallIn(SingletonComponent::class)
2124
@Module
2225
internal object DataStoreModule {
@@ -30,4 +33,9 @@ internal object DataStoreModule {
3033
@Singleton
3134
@Provides
3235
fun provideTokenDataStore(@ApplicationContext context: Context): DataStore<Preferences> = context.tokenDataStore
36+
37+
@UserDataStore
38+
@Singleton
39+
@Provides
40+
fun provideUserDataStore(@ApplicationContext context: Context): DataStore<Preferences> = context.userDataStore
3341
}

core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreQualifier.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ annotation class AuthDataStore
99
@Qualifier
1010
@Retention(AnnotationRetention.BINARY)
1111
annotation class TokenDataStore
12+
13+
@Qualifier
14+
@Retention(AnnotationRetention.BINARY)
15+
annotation class UserDataStore

core/data/src/main/java/com/neki/android/core/data/remote/api/PoseService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ class PoseService @Inject constructor(
3737
}
3838

3939
// 랜덤 포즈 조회
40-
suspend fun getRandomPose(headCount: String): BasicResponse<PoseDetailResponse> {
40+
suspend fun getRandomPose(headCount: String, excludeIds: String): BasicResponse<PoseDetailResponse> {
4141
return client.get("/api/poses/random") {
4242
parameter("headCount", headCount)
43+
parameter("excludeIds", excludeIds)
4344
}.body()
4445
}
4546

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ 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
6+
import com.neki.android.core.common.exception.ClientApiException
77
import com.neki.android.core.data.paging.PosePagingSource
88
import com.neki.android.core.data.paging.ScrapPosePagingSource
99
import com.neki.android.core.data.remote.api.PoseService
1010
import com.neki.android.core.data.util.runSuspendCatching
11+
import com.neki.android.core.dataapi.repository.NO_MORE_RANDOM_POSE
1112
import com.neki.android.core.dataapi.repository.PoseRepository
1213
import com.neki.android.core.model.PeopleCount
1314
import com.neki.android.core.model.Pose
1415
import com.neki.android.core.model.SortOrder
16+
import io.ktor.client.plugins.ClientRequestException
1517
import kotlinx.coroutines.flow.Flow
1618
import javax.inject.Inject
1719

@@ -69,38 +71,47 @@ class PoseRepositoryImpl @Inject constructor(
6971
override suspend fun getSingleRandomPose(
7072
headCount: PeopleCount,
7173
excludeIds: Set<Long>,
72-
maxRetry: Int,
7374
): 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-
}
75+
val excludeIdsString = excludeIds.joinToString(",")
76+
77+
return@runSuspendCatching try {
78+
poseService.getRandomPose(
79+
headCount = headCount.name,
80+
excludeIds = excludeIdsString,
81+
).data.toModel()
82+
} catch (e: ClientRequestException) {
83+
if (e.response.status.value == NO_MORE_RANDOM_POSE)
84+
throw ClientApiException(NO_MORE_RANDOM_POSE, e.message)
85+
else throw e
7986
}
80-
throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요")
8187
}
8288

8389
override suspend fun getMultipleRandomPose(
8490
headCount: PeopleCount,
8591
excludeIds: Set<Long>,
8692
poseSize: Int,
87-
maxRetry: Int,
8893
): Result<List<Pose>> = runSuspendCatching {
8994
val result = mutableListOf<Pose>()
9095
val collectedIds = excludeIds.toMutableSet()
91-
var retryCount = 0
96+
repeat(poseSize) {
97+
val excludeIdsString = collectedIds.joinToString(",")
98+
val pose = try {
99+
poseService.getRandomPose(
100+
headCount = headCount.name,
101+
excludeIds = excludeIdsString,
102+
).data.toModel()
103+
} catch (e: ClientRequestException) {
104+
// Http Error Code 이지만, 클라이언트에서 성공으로 취급
105+
if (e.response.status.value == NO_MORE_RANDOM_POSE) return@runSuspendCatching result
106+
else throw e
107+
}
92108

93-
while (result.size < poseSize && retryCount < maxRetry) {
94-
val pose = poseService.getRandomPose(headCount = headCount.name).data.toModel()
95109
if (pose.id !in collectedIds) {
96110
result.add(pose)
97111
collectedIds.add(pose.id)
98-
} else {
99-
retryCount++
100112
}
101113
}
102-
103-
result.ifEmpty { throw RandomPoseRetryExhaustedException("새로운 포즈를 찾지 못했어요") }
114+
return@runSuspendCatching result
104115
}
105116

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

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
package com.neki.android.core.data.repository.impl
22

3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.booleanPreferencesKey
6+
import androidx.datastore.preferences.core.edit
7+
import com.neki.android.core.data.local.di.UserDataStore
38
import com.neki.android.core.data.remote.api.UserService
49
import com.neki.android.core.data.remote.model.request.UpdateProfileImageRequest
510
import com.neki.android.core.data.remote.model.request.UpdateUserInfoRequest
611
import com.neki.android.core.data.util.runSuspendCatching
712
import com.neki.android.core.dataapi.repository.UserRepository
813
import com.neki.android.core.model.UserInfo
14+
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.map
916
import javax.inject.Inject
1017

1118
class UserRepositoryImpl @Inject constructor(
19+
@UserDataStore private val dataStore: DataStore<Preferences>,
1220
private val userService: UserService,
1321
) : UserRepository {
22+
override val hasVisitedRandomPose: Flow<Boolean> =
23+
dataStore.data.map { preferences ->
24+
preferences[HAS_VISITED_RANDOM_POSE] ?: false
25+
}
26+
27+
override suspend fun setRandomPoseVisited() {
28+
dataStore.edit { preferences ->
29+
preferences[HAS_VISITED_RANDOM_POSE] = true
30+
}
31+
}
32+
1433
override suspend fun getUserInfo(): Result<UserInfo> = runSuspendCatching {
1534
userService.getUserInfo().data.toModel()
1635
}
@@ -22,4 +41,8 @@ class UserRepositoryImpl @Inject constructor(
2241
override suspend fun updateProfileImage(mediaId: Long?): Result<Unit> = runSuspendCatching {
2342
userService.updateProfileImage(UpdateProfileImageRequest(mediaId))
2443
}
44+
45+
companion object {
46+
private val HAS_VISITED_RANDOM_POSE = booleanPreferencesKey("is_first_visit_random_pose")
47+
}
2548
}

detekt-config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ formatting:
5252
exceptions:
5353
TooGenericExceptionCaught:
5454
active: false
55+
SwallowedException:
56+
active: false
5557

5658
style:
5759
FunctionOnlyReturningConstant:

0 commit comments

Comments
 (0)