Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.ninecraft.booket.core.common.event

import com.ninecraft.booket.core.common.constants.ErrorScope
import com.ninecraft.booket.core.common.utils.isNetworkError
import retrofit2.HttpException

fun postErrorDialog(
errorScope: ErrorScope,
exception: Throwable,
confirmLabel: String = "확인",
onConfirm: () -> Unit = {},
) {
val (title, message) = when {
exception.isNetworkError() -> {
null to "네트워크 연결이 불안정합니다.\n인터넷 연결을 확인해주세요"
}

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

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

ErrorScope.AUTH_SESSION_EXPIRED -> {
null to "세션이 만료되었어요.\n다시 로그인 해주세요"
}
}
}

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

val spec = DialogSpec(
title = title,
message = message,
confirmLabel = confirmLabel,
onConfirm = onConfirm,
)

EventHelper.sendEvent(event = ReedEvent.ShowDialog(spec))
}

fun postLoginRequiredDialog(onConfirm: () -> Unit) {
val spec = DialogSpec(
message = "로그인이 필요한 기능입니다.\n로그인 해주세요.",
confirmLabel = "로그인 하기",
onConfirm = onConfirm,
dismissLabel = "닫기",
)

EventHelper.sendEvent(event = ReedEvent.ShowDialog(spec))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ninecraft.booket.core.common.event

data class DialogSpec(
val message: String,
val confirmLabel: String,
val onConfirm: () -> Unit,
val title: String? = null,
val dismissLabel: String? = null,
val onDismissRequest: () -> Unit = {},
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ninecraft.booket.core.common.event

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow

object EventHelper {
private val _eventFlow = Channel<ReedEvent>(Channel.BUFFERED)
val eventFlow = _eventFlow.receiveAsFlow()

fun sendEvent(event: ReedEvent) {
_eventFlow.trySend(event)
}
Comment on lines +10 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

이벤트 전송 실패 처리가 누락되었습니다.

trySend()는 채널이 닫혔거나 버퍼가 가득 찬 경우 실패할 수 있지만, 반환된 결과를 확인하지 않아 이벤트가 조용히 손실될 수 있습니다. 로그인 요청 다이얼로그와 같은 중요한 이벤트가 사용자에게 표시되지 않을 수 있습니다.

다음 diff를 적용하여 실패한 전송을 처리하세요:

 fun sendEvent(event: ReedEvent) {
-    _eventFlow.trySend(event)
+    _eventFlow.trySend(event).onFailure {
+        // 실패 시 로깅 또는 대체 처리
+        android.util.Log.e("EventHelper", "Failed to send event: $event", it)
+    }
 }

또는 suspend 함수로 변경하여 확실한 전송을 보장할 수도 있습니다:

-fun sendEvent(event: ReedEvent) {
-    _eventFlow.trySend(event)
+suspend fun sendEvent(event: ReedEvent) {
+    _eventFlow.send(event)
 }
🤖 Prompt for AI Agents
In
core/common/src/main/kotlin/com/ninecraft/booket/core/common/event/EventHelper.kt
around lines 12 to 14, the call to _eventFlow.trySend(event) ignores the
SendResult so events can be silently lost; update the function to handle
failures by checking the TrySendResult and reacting (e.g., if result.isSuccess
do nothing, else log an error and/or enqueue the event, throw or notify caller)
or convert sendEvent to a suspend function and use _eventFlow.emit(event) (or
_eventFlow.send(event)) to guarantee delivery and propagate exceptions.

}

sealed interface ReedEvent {
data class ShowDialog(val spec: DialogSpec) : ReedEvent
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.ninecraft.booket.core.common.utils

import androidx.annotation.StringRes
import com.ninecraft.booket.core.common.R
import com.ninecraft.booket.core.common.constants.ErrorDialogSpec
import com.ninecraft.booket.core.common.constants.ErrorScope
import com.ninecraft.booket.core.common.event.ErrorEvent
import com.ninecraft.booket.core.common.event.ErrorEventHelper
import com.ninecraft.booket.core.common.event.postErrorDialog
import com.ninecraft.booket.core.network.response.ErrorResponse
import com.orhanobut.logger.Logger
import kotlinx.serialization.SerializationException
Expand All @@ -26,7 +22,7 @@ fun handleException(
postErrorDialog(
errorScope = ErrorScope.AUTH_SESSION_EXPIRED,
exception = exception,
action = {
onConfirm = {
onLoginRequired()
},
)
Expand All @@ -51,48 +47,6 @@ fun handleException(
}
}

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

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

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

ErrorScope.AUTH_SESSION_EXPIRED -> {
null to "세션이 만료되었어요.\n다시 로그인 해주세요"
}
}
}

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

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

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

private fun HttpException.parseErrorMessage(): String? {
return try {
val errorBody = response()?.errorBody()?.string()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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.UiText
import com.ninecraft.booket.core.common.event.postLoginRequiredDialog
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
Expand Down Expand Up @@ -35,7 +35,6 @@ import dagger.hilt.android.components.ActivityRetainedComponent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch
import com.ninecraft.booket.core.designsystem.R as designR

class LibraryPresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
Expand Down Expand Up @@ -127,10 +126,13 @@ class LibraryPresenter @AssistedInject constructor(

is LibraryUiEvent.OnLibrarySearchClick -> {
if (userState is UserState.Guest) {
scope.launch {
sideEffect = LibrarySideEffect.ShowToast(UiText.StringResource(designR.string.login_required))
navigator.redirectToLogin()
}
postLoginRequiredDialog(
onConfirm = {
scope.launch {
navigator.redirectToLogin()
}
},
)
} else {
navigator.goTo(LibrarySearchScreen)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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.constants.ErrorScope
import com.ninecraft.booket.core.common.utils.postErrorDialog
import com.ninecraft.booket.core.common.event.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
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.ninecraft.booket.core.common.constants.ErrorDialogSpec
import com.ninecraft.booket.core.common.event.ErrorEvent
import com.ninecraft.booket.core.common.event.ErrorEventHelper
import com.ninecraft.booket.core.common.event.DialogSpec
import com.ninecraft.booket.core.common.event.EventHelper
import com.ninecraft.booket.core.common.event.ReedEvent
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
import com.ninecraft.booket.core.ui.component.ReedDialog
import com.ninecraft.booket.feature.screens.SplashScreen
Expand Down Expand Up @@ -55,13 +54,13 @@ class MainActivity : ComponentActivity() {
val backStack = rememberSaveableBackStack(root = SplashScreen)
val navigator = rememberCircuitNavigator(backStack)

val dialogSpec = remember { mutableStateOf<ErrorDialogSpec?>(null) }
val dialogSpec = remember { mutableStateOf<DialogSpec?>(null) }

// 전역 에러 수신
// 전역 이벤트 수신
LaunchedEffect(Unit) {
ErrorEventHelper.errorEvent.collect { event ->
EventHelper.eventFlow.collect { event ->
when (event) {
is ErrorEvent.ShowDialog -> {
is ReedEvent.ShowDialog -> {
dialogSpec.value = event.spec
}
}
Expand All @@ -72,10 +71,10 @@ class MainActivity : ComponentActivity() {
ReedDialog(
title = spec.title,
description = spec.message,
confirmButtonText = stringResource(spec.buttonLabelResId),

confirmButtonText = spec.confirmLabel,
dismissButtonText = spec.dismissLabel,
onConfirmRequest = {
spec.action()
spec.onConfirm()
dialogSpec.value = null
},
onDismissRequest = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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.constants.BookStatus
import com.ninecraft.booket.core.common.event.postLoginRequiredDialog
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
Expand Down Expand Up @@ -38,7 +39,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch
import com.ninecraft.booket.core.designsystem.R as designR

class BookSearchPresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
Expand Down Expand Up @@ -120,13 +120,18 @@ class BookSearchPresenter @AssistedInject constructor(
}

fun upsertBook(isbn13: String, bookStatus: String) {
scope.launch {
if (userState is UserState.Guest) {
sideEffect = BookSearchSideEffect.ShowToast(UiText.StringResource(designR.string.login_required))
navigator.redirectToLogin()
return@launch
}
if (userState is UserState.Guest) {
postLoginRequiredDialog(
onConfirm = {
scope.launch {
navigator.redirectToLogin()
}
},
)
return
}

scope.launch {
repository.upsertBook(isbn13, bookStatus)
.onSuccess {
registeredUserBookId = it.userBookId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ 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.common.event.postErrorDialog
import com.ninecraft.booket.core.data.api.repository.AuthRepository
import com.ninecraft.booket.core.data.api.repository.RemoteConfigRepository
import com.ninecraft.booket.core.data.api.repository.UserRepository
import com.ninecraft.booket.core.model.AutoLoginState
import com.ninecraft.booket.core.model.OnboardingState
import com.ninecraft.booket.core.ui.R
import com.ninecraft.booket.feature.screens.HomeScreen
import com.ninecraft.booket.feature.screens.LoginScreen
import com.ninecraft.booket.feature.screens.OnboardingScreen
Expand Down Expand Up @@ -64,8 +63,8 @@ class SplashPresenter @AssistedInject constructor(
postErrorDialog(
errorScope = ErrorScope.GLOBAL,
exception = exception,
buttonLabelResId = R.string.retry,
action = { checkTermsAgreement() },
confirmLabel = "다시 시도하기",
onConfirm = { checkTermsAgreement() },
)
}
}
Expand Down
Loading