Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.ninecraft.booket.core.common.constants
import androidx.annotation.StringRes

data class ErrorDialogSpec(
val title: String? = null,
val message: String,
@StringRes val buttonLabelResId: Int,
val action: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.ninecraft.booket.core.common.constants

enum class ErrorScope {
GLOBAL, LOGIN, BOOK_REGISTER, RECORD_REGISTER
GLOBAL, LOGIN, AUTH_SESSION_EXPIRED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ fun handleException(
) {
when {
exception is HttpException && exception.code() == 401 -> {
onLoginRequired()
postErrorDialog(
errorScope = ErrorScope.AUTH_SESSION_EXPIRED,
exception = exception,
action = {
onLoginRequired()
},
)
}

exception is HttpException -> {
Expand Down Expand Up @@ -51,53 +57,40 @@ fun postErrorDialog(
@StringRes buttonLabelResId: Int = R.string.confirm,
action: () -> Unit = {},
) {
val spec = buildDialog(
scope = errorScope,
exception = exception,
buttonLabelResId = buttonLabelResId,
action = action,
)

ErrorEventHelper.sendError(event = ErrorEvent.ShowDialog(spec))
}

private fun buildDialog(
scope: ErrorScope,
exception: Throwable,
@StringRes buttonLabelResId: Int,
action: () -> Unit,
): ErrorDialogSpec {
val message = when {
val (title, message) = when {
exception.isNetworkError() -> {
"네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요"
null to "네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요"
}

exception is HttpException -> {
when (scope) {
when (errorScope) {
ErrorScope.GLOBAL -> {
"알 수 없는 문제가 발생했어요.\n다시 시도해주세요"
null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요"
}

ErrorScope.LOGIN -> {
"예기치 않은 오류가 발생했습니다.\n다시 로그인 해주세요."
"로그인 오류" to "예기치 않은 오류가 발생했습니다.\n다시 로그인 해주세요."
}

ErrorScope.BOOK_REGISTER -> {
"도서 등록 중 오류가 발생했어요.\n다시 시도해주세요"
}

ErrorScope.RECORD_REGISTER -> {
"기록 저장에 실패했어요.\n다시 시도해주세요"
ErrorScope.AUTH_SESSION_EXPIRED -> {
null to "세션이 만료되었어요.\n다시 로그인 해주세요"
}
}
}

else -> {
"알 수 없는 문제가 발생했어요.\n다시 시도해주세요"
null to "알 수 없는 문제가 발생했어요.\n다시 시도해주세요"
}
}

return ErrorDialogSpec(message = message, buttonLabelResId = buttonLabelResId, action = action)
val spec = ErrorDialogSpec(
title = title,
message = message,
buttonLabelResId = buttonLabelResId,
action = action,
)

ErrorEventHelper.sendError(event = ErrorEvent.ShowDialog(spec))
}

@Suppress("TooGenericExceptionCaught")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.data.api.repository.BookRepository
import com.ninecraft.booket.core.model.RecentBookModel
import com.ninecraft.booket.core.model.UserState
import com.ninecraft.booket.feature.screens.BookDetailScreen
import com.ninecraft.booket.feature.screens.BookSearchScreen
import com.ninecraft.booket.feature.screens.HomeScreen
import com.ninecraft.booket.feature.screens.LoginScreen
import com.ninecraft.booket.feature.screens.RecordScreen
import com.ninecraft.booket.feature.screens.SettingsScreen
import com.skydoves.compose.effects.RememberedEffect
Expand Down Expand Up @@ -56,6 +58,14 @@ class HomePresenter @AssistedInject constructor(
recentBooks = result.recentBooks.toPersistentList()
}.onFailure { exception ->
uiState = UiState.Error(exception)

handleException(
exception = exception,
onError = {},
onLoginRequired = {
navigator.resetRoot(LoginScreen())
},
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.utils.UiText
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.data.api.repository.BookRepository
import com.ninecraft.booket.core.model.LibraryBookSummaryModel
Expand All @@ -16,6 +17,7 @@ import com.ninecraft.booket.core.ui.component.FooterState
import com.ninecraft.booket.feature.screens.BookDetailScreen
import com.ninecraft.booket.feature.screens.LibraryScreen
import com.ninecraft.booket.feature.screens.LibrarySearchScreen
import com.ninecraft.booket.feature.screens.LoginScreen
import com.ninecraft.booket.feature.screens.SettingsScreen
import com.ninecraft.booket.feature.screens.extensions.redirectToLogin
import com.orhanobut.logger.Logger
Expand Down Expand Up @@ -105,6 +107,14 @@ class LibraryPresenter @AssistedInject constructor(
} else {
footerState = FooterState.Error(errorMessage)
}

handleException(
exception = exception,
onError = {},
onLoginRequired = {
navigator.resetRoot(LoginScreen())
},
)
}
}
}
Expand Down Expand Up @@ -136,7 +146,10 @@ class LibraryPresenter @AssistedInject constructor(
}

currentFilter = event.filterOption
filterLibraryBooks(status = currentFilter.getApiValue(), page = START_INDEX, size = PAGE_SIZE)

if (userState !is UserState.Guest) {
filterLibraryBooks(status = currentFilter.getApiValue(), page = START_INDEX, size = PAGE_SIZE)
}
}

is LibraryUiEvent.OnBookClick -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.constants.ErrorScope
import com.ninecraft.booket.core.common.utils.postErrorDialog
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.data.api.repository.UserRepository
import com.ninecraft.booket.feature.screens.HomeScreen
Expand Down Expand Up @@ -60,10 +62,11 @@ class LoginPresenter @AssistedInject constructor(
}
}
}.onFailure { exception ->
exception.message?.let { Logger.e(it) }
sideEffect = exception.message?.let {
LoginSideEffect.ShowToast(it)
}
Logger.e(exception.message ?: "Failed to get user profile")
postErrorDialog(
errorScope = ErrorScope.LOGIN,
exception = exception,
)
}
}
}
Expand All @@ -72,7 +75,7 @@ class LoginPresenter @AssistedInject constructor(
when (event) {
is LoginUiEvent.OnKakaoLoginButtonClick -> {
isLoading = true
sideEffect = LoginSideEffect.KakaoLogin
sideEffect = LoginSideEffect.KakaoLogin()
}

is LoginUiEvent.LoginFailure -> {
Expand All @@ -89,11 +92,12 @@ class LoginPresenter @AssistedInject constructor(
.onSuccess {
navigateAfterLogin()
}.onFailure { exception ->
exception.message?.let { Logger.e(it) }
Logger.e(exception.message ?: "Login failed")
analyticsHelper.logEvent(EVENT_ERROR_LOGIN)
sideEffect = exception.message?.let {
LoginSideEffect.ShowToast(it)
}
postErrorDialog(
errorScope = ErrorScope.LOGIN,
exception = exception,
)
}
} finally {
isLoading = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data class LoginUiState(

@Immutable
sealed interface LoginSideEffect {
data object KakaoLogin : LoginSideEffect
data class KakaoLogin(private val key: String = UUID.randomUUID().toString()) : LoginSideEffect
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@easyhooon 로그인 API 에러 처리 방식을 개선하면서 LoginSideEffect를 초기화해주는 로직이 필요해졌습니다. (안그러면 카카오 로그인 재시도 시 무한로딩 발생) 토스트메세지 이벤트처럼 UUID 값을 사용하는 방식으로 변경했는데 확인 부탁드립니다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 무한로딩 발생하는게 이거 때문이었군여
분명 타임아웃 시간이 지나서 로딩이 끝날때가 되었는데 계속 돌길래 뭔가했는데

Copy link
Copy Markdown
Contributor Author

@easyhooon easyhooon Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        fun handleEvent(event: LoginUiEvent) {
            when (event) {
                is LoginUiEvent.OnKakaoLoginButtonClick -> {
                    isLoading = true
                    sideEffect = LoginSideEffect.KakaoLogin()
                }
  1. 사용자가 카카오 로그인 버튼 클릭
  2. sideEffect = LoginSideEffect.KakaoLogin() → RememberedEffect 실행
  3. 카카오 로그인 실패 → eventSink(LoginUiEvent.LoginFailure(...))
  4. sideEffect = LoginSideEffect.ShowToast(...) → RememberedEffect 실행 (Toast 표시)
  5. 사용자가 다시 카카오 로그인 버튼 클릭
  6. sideEffect = LoginSideEffect.KakaoLogin()
    -> 문제:프로퍼티가 없는 LoginSideEffect.KakaoLogin()은 구조적으로 동일한 값으로 인식됨
  7. Compose가 state.sideEffect가 변경되지 않았다고 판단
  8. RememberedEffect가 재실행되지 않음
  9. 카카오 로그인이 다시 시작되지 않음
  10. isLoading = true만 남아 무한 로딩

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uuid 방식으로 해결할 수 있는 케이스라 괜찮은거같습니다!

data class ShowToast(
val message: String,
private val key: String = UUID.randomUUID().toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class MainActivity : ComponentActivity() {

dialogSpec.value?.let { spec ->
ReedDialog(
title = spec.title,
description = spec.message,
confirmButtonText = stringResource(spec.buttonLabelResId),

Expand Down
Loading