Skip to content

Commit d107c81

Browse files
committed
[BOOK-233] refactor: 도서 상세 화면에서 초기 로드를 async로 구현
- getSeedsStats, getBookDetail, getReadingRecords를 async로 병렬 호출 - 응답 대기 후 하나라도 실패 시 UiState.Error로 전환
1 parent c7e4c64 commit d107c81

3 files changed

Lines changed: 214 additions & 188 deletions

File tree

feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt

Lines changed: 66 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import androidx.compose.runtime.mutableStateOf
88
import androidx.compose.runtime.rememberCoroutineScope
99
import androidx.compose.runtime.setValue
1010
import com.ninecraft.booket.core.common.constants.BookStatus
11+
import com.ninecraft.booket.core.common.constants.ErrorScope
1112
import com.ninecraft.booket.core.common.utils.handleException
13+
import com.ninecraft.booket.core.common.utils.postErrorDialog
1214
import com.ninecraft.booket.core.data.api.repository.BookRepository
1315
import com.ninecraft.booket.core.data.api.repository.RecordRepository
1416
import com.ninecraft.booket.core.model.BookDetailModel
@@ -32,6 +34,9 @@ import kotlinx.collections.immutable.ImmutableList
3234
import kotlinx.collections.immutable.persistentListOf
3335
import kotlinx.collections.immutable.toImmutableList
3436
import kotlinx.collections.immutable.toPersistentList
37+
import kotlinx.coroutines.CancellationException
38+
import kotlinx.coroutines.async
39+
import kotlinx.coroutines.coroutineScope
3540
import kotlinx.coroutines.launch
3641
import java.time.LocalDateTime
3742

@@ -69,50 +74,48 @@ class BookDetailPresenter @AssistedInject constructor(
6974
var isRecordSortBottomSheetVisible by rememberRetained { mutableStateOf(false) }
7075
var sideEffect by rememberRetained { mutableStateOf<BookDetailSideEffect?>(null) }
7176

72-
fun getSeedsStats() {
73-
scope.launch {
74-
bookRepository.getSeedsStats(screen.userBookId)
75-
.onSuccess { result ->
76-
seedsStates = result.categories.toImmutableList()
77-
}
78-
.onFailure { exception ->
79-
val handleErrorMessage = { message: String ->
80-
Logger.e(message)
81-
sideEffect = BookDetailSideEffect.ShowToast(message)
82-
}
77+
suspend fun initialLoad() {
78+
uiState = UiState.Loading
8379

84-
handleException(
85-
exception = exception,
86-
onError = handleErrorMessage,
87-
onLoginRequired = {
88-
navigator.resetRoot(LoginScreen)
89-
},
90-
)
80+
try {
81+
coroutineScope {
82+
val bookDetailDef = async { bookRepository.getBookDetail(screen.isbn13).getOrThrow() }
83+
val seedsDef = async { bookRepository.getSeedsStats(screen.userBookId).getOrThrow() }
84+
val readingRecordsDef = async {
85+
recordRepository.getReadingRecords(
86+
userBookId = screen.userBookId,
87+
sort = currentRecordSort.value,
88+
page = START_INDEX,
89+
size = PAGE_SIZE,
90+
).getOrThrow()
9191
}
92-
}
93-
}
92+
val detail = bookDetailDef.await()
93+
val seeds = seedsDef.await()
94+
val records = readingRecordsDef.await()
9495

95-
fun getBookDetail() {
96-
scope.launch {
97-
bookRepository.getBookDetail(screen.isbn13)
98-
.onSuccess { result ->
99-
bookDetail = result
100-
currentBookStatus = BookStatus.fromValue(result.userBookStatus) ?: BookStatus.BEFORE_READING
101-
}
102-
.onFailure { exception ->
103-
val handleErrorMessage = { message: String ->
104-
Logger.e(message)
105-
sideEffect = BookDetailSideEffect.ShowToast(message)
106-
}
96+
bookDetail = detail
97+
seedsStates = seeds.categories.toImmutableList()
98+
readingRecords = records.content.toPersistentList()
10799

108-
handleException(
109-
exception = exception,
110-
onError = handleErrorMessage,
111-
onLoginRequired = {
112-
navigator.resetRoot(LoginScreen)
113-
},
114-
)
115-
}
100+
uiState = UiState.Success
101+
}
102+
} catch (ce: CancellationException) {
103+
throw ce
104+
} catch (e: Throwable) {
105+
uiState = UiState.Error(e)
106+
107+
val handleErrorMessage = { message: String ->
108+
Logger.e(message)
109+
sideEffect = BookDetailSideEffect.ShowToast(message)
110+
}
111+
112+
handleException(
113+
exception = e,
114+
onError = handleErrorMessage,
115+
onLoginRequired = {
116+
navigator.resetRoot(LoginScreen)
117+
},
118+
)
116119
}
117120
}
118121

@@ -125,6 +128,11 @@ class BookDetailPresenter @AssistedInject constructor(
125128
isBookUpdateBottomSheetVisible = false
126129
}
127130
.onFailure { exception ->
131+
postErrorDialog(
132+
errorScope = ErrorScope.BOOK_REGISTER,
133+
exception = exception,
134+
)
135+
128136
val handleErrorMessage = { message: String ->
129137
Logger.e(message)
130138
sideEffect = BookDetailSideEffect.ShowToast(message)
@@ -141,50 +149,35 @@ class BookDetailPresenter @AssistedInject constructor(
141149
}
142150
}
143151

144-
fun getReadingRecords(startIndex: Int = START_INDEX) {
152+
fun loadMoreReadingRecords(startIndex: Int) {
153+
// 초기 페이지 로드는 initialLoad()에서 담당하므로 무시
154+
if (startIndex == START_INDEX || isLastPage) {
155+
return
156+
}
157+
145158
scope.launch {
146-
if (startIndex == START_INDEX) {
147-
uiState = UiState.Loading
148-
} else {
149-
footerState = FooterState.Loading
150-
}
159+
footerState = FooterState.Loading
151160

152161
recordRepository.getReadingRecords(
153162
userBookId = screen.userBookId,
154163
sort = currentRecordSort.value,
155164
page = startIndex,
156165
size = PAGE_SIZE,
157166
).onSuccess { result ->
158-
readingRecords = if (startIndex == START_INDEX) {
159-
result.content.toPersistentList()
160-
} else {
161-
(readingRecords + result.content).toPersistentList()
162-
}
163-
167+
readingRecords = (readingRecords + result.content).toPersistentList()
164168
currentStartIndex = startIndex
165169
isLastPage = result.content.size < PAGE_SIZE
166-
167-
if (startIndex == START_INDEX) {
168-
uiState = UiState.Success
169-
} else {
170-
footerState = if (isLastPage) FooterState.End else FooterState.Idle
171-
}
170+
footerState = if (isLastPage) FooterState.End else FooterState.Idle
172171
}.onFailure { exception ->
173172
Logger.d(exception)
174173
val errorMessage = exception.message ?: "알 수 없는 오류가 발생했습니다."
175-
if (startIndex == START_INDEX) {
176-
uiState = UiState.Error(errorMessage)
177-
} else {
178-
footerState = FooterState.Error(errorMessage)
179-
}
174+
footerState = FooterState.Error(errorMessage)
180175
}
181176
}
182177
}
183178

184179
LaunchedEffect(Unit) {
185-
getSeedsStats()
186-
getBookDetail()
187-
getReadingRecords()
180+
initialLoad()
188181
}
189182

190183
fun handleEvent(event: BookDetailUiEvent) {
@@ -237,7 +230,13 @@ class BookDetailPresenter @AssistedInject constructor(
237230

238231
is BookDetailUiEvent.OnLoadMore -> {
239232
if (uiState != UiState.Loading && footerState !is FooterState.Loading && !isLastPage) {
240-
getReadingRecords(startIndex = currentStartIndex + 1)
233+
loadMoreReadingRecords(startIndex = currentStartIndex + 1)
234+
}
235+
}
236+
237+
is BookDetailUiEvent.OnRetryClick -> {
238+
scope.launch {
239+
initialLoad()
241240
}
242241
}
243242
}

0 commit comments

Comments
 (0)