Skip to content

[Feat] Error Modal#176

Merged
nahy-512 merged 1 commit intodevelopfrom
feat/#172-error-modal
Jan 22, 2026
Merged

[Feat] Error Modal#176
nahy-512 merged 1 commit intodevelopfrom
feat/#172-error-modal

Conversation

@kimjw2003
Copy link
Contributor

@kimjw2003 kimjw2003 commented Jan 22, 2026

📮 관련 이슈

📌 작업 내용

  • 에러 모달 작업

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 네트워크 오류 감지 및 처리 기능 추가
    • 네트워크 오류 발생 시 사용자에게 알림 창 표시
    • 알림 창에서 앱 재시작 옵션 제공
    • 인터넷 연결 끊김, 타임아웃, 연결 오류 등 다양한 네트워크 상태 지원

✏️ Tip: You can customize this high-level summary in your review settings.

@kimjw2003 kimjw2003 self-assigned this Jan 22, 2026
@kimjw2003 kimjw2003 requested a review from a team as a code owner January 22, 2026 15:11
@kimjw2003 kimjw2003 added the Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경 label Jan 22, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

📋 Walkthrough

네트워크 에러 감지 및 표시 시스템을 구현합니다. OkHttp 인터셉터를 통해 HTTP 응답 및 예외를 감시하고, SharedFlow로 에러를 관리하며, MainActivity에서 UI 모달로 사용자에게 에러를 표시합니다.

📊 Changes

Cohort / File(s) 변경 내용
네트워크 에러 관리자
app/src/main/java/com/flint/data/di/interceptor/NetworkErrorManager.kt
새로운 싱글톤 클래스로 SharedFlow를 통한 에러 방출 기능 추가. NetworkError sealed class 정의 (NoInternet, Timeout, ConnectionError, UnknownError)
네트워크 에러 인터셉터
app/src/main/java/com/flint/data/di/interceptor/NetworkErrorInterceptor.kt
OkHttp Interceptor 구현으로 HTTP 응답 상태 코드 및 네트워크 예외 감시. 감지된 에러를 NetworkErrorManager로 방출
의존성 주입 설정
app/src/main/java/com/flint/data/di/NetworkModule.kt
provideOkHttpClient 메서드에 networkErrorInterceptor 파라미터 추가 및 OkHttpClient에 등록
UI 에러 표시
app/src/main/java/com/flint/presentation/MainActivity.kt
NetworkErrorManager 주입, LaunchedEffect로 에러 수집, OneButtonModal 표시 및 앱 재시작 기능 추가

🔄 Sequence Diagram(s)

sequenceDiagram
    actor User
    participant OkHttpClient
    participant NetworkErrorInterceptor
    participant NetworkErrorManager
    participant MainActivity

    User->>OkHttpClient: HTTP 요청 발송
    OkHttpClient->>NetworkErrorInterceptor: intercept() 호출
    
    alt HTTP 응답 상태 코드 300-599
        NetworkErrorInterceptor->>NetworkErrorManager: emitError(ConnectionError)
        NetworkErrorManager->>MainActivity: networkError SharedFlow 방출
        MainActivity->>User: 에러 모달 표시
    else UnknownHostException
        NetworkErrorInterceptor->>NetworkErrorManager: emitError(NoInternet)
        NetworkErrorManager->>MainActivity: networkError SharedFlow 방출
        MainActivity->>User: 에러 모달 표시
    else SocketTimeoutException
        NetworkErrorInterceptor->>NetworkErrorManager: emitError(Timeout)
        NetworkErrorManager->>MainActivity: networkError SharedFlow 방출
        MainActivity->>User: 에러 모달 표시
    else IOException
        NetworkErrorInterceptor->>NetworkErrorManager: emitError(UnknownError)
        NetworkErrorManager->>MainActivity: networkError SharedFlow 방출
        MainActivity->>User: 에러 모달 표시
    end
    
    User->>MainActivity: 모달 확인 버튼 클릭
    MainActivity->>MainActivity: restartApplication() 실행
    MainActivity->>User: 앱 재시작
Loading

⏱️ Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

새로운 에러 관리 시스템 구축으로 인한 다중 파일 수정, SharedFlow 및 Coroutine 로직 검토, 의존성 주입 설정 확인이 필요합니다.

🐰 Poem

네트워크 오류 잡아내는 \
토끼 인터셉터의 재주 \
SharedFlow 흐르는 곳에 \
에러 모달 피어난다 🌸 \
사용자 경험을 위해 \
우리는 달린다 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning 필수 섹션인 '작업 내용'이 매우 간략하며, 스크린샷, 미구현 사항, 리뷰어 노트 등이 누락되어 설명이 불완전함. 작업 내용을 자세히 설명하고, 구현된 기능(NetworkErrorInterceptor, NetworkErrorManager, MainActivity 통합), 스크린샷, 테스트 여부를 포함하여 작성 필요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 제목이 추상적이고 구체적인 정보 없이 '에러 모달'이라는 일반적인 용어만 사용하여 변경 사항의 세부 내용을 명확히 전달하지 못함. 제목을 구체화하여 네트워크 에러 모달 구현, 에러 감지 및 표시 메커니즘 등 주요 변경 사항을 포함하도록 수정 필요.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed PR은 issue #172의 '앱 공용적인 에러모달 제작' 요구사항을 충족하기 위해 NetworkErrorInterceptor, NetworkErrorManager, MainActivity에서 에러 모달을 구현했음.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 에러 모달 구현 범위 내에 있으며, 요구사항 외의 불필요한 변경 사항은 없음.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/presentation/MainActivity.kt`:
- Around line 49-60: The modal cannot be dismissed and the error state isn't
cleared because onDismiss is a no-op and networkError remains set; update the
OneButtonModal invocation so onDismiss clears the error state (e.g., call the
state setter to set networkError to null/empty) and also ensure the onConfirm
(restartApplication) clears the same networkError before/after calling
restartApplication; reference the networkError state variable and the
OneButtonModal usage and modify both onDismiss and the onConfirm callback to
reset the error state.
- Around line 65-72: In restartApplication, avoid the unsafe intent!! and abrupt
exitProcess(0): first retrieve the launch intent via
PackageManager.getLaunchIntentForPackage(this.packageName) into a nullable
variable and bail with a logged warning or fallback if null instead of
force-unwrapping; then build the restart intent using
Intent.makeRestartActivityTask(component) (or create a new Intent(this,
MainActivity::class.java) with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK
if component is unavailable) and call startActivity(restartIntent) followed by
finishAffinity() (or finish() where appropriate) to close activities gracefully,
removing the exitProcess call; update references in restartApplication to use
these safer flows.
🧹 Nitpick comments (4)
app/src/main/java/com/flint/data/di/interceptor/NetworkErrorManager.kt (1)

8-16: SharedFlow 버퍼 설정 검토 필요

MutableSharedFlow의 기본 설정(replay=0, extraBufferCapacity=0)으로는 collector가 없을 때 emit()이 호출되면 에러가 유실될 수 있습니다. 앱 시작 직후 네트워크 에러가 발생하면 UI가 아직 collect하지 않는 상태일 수 있습니다.

♻️ 버퍼 추가 제안
 `@Singleton`
 class NetworkErrorManager `@Inject` constructor() {
-    private val _networkError = MutableSharedFlow<NetworkError>()
+    private val _networkError = MutableSharedFlow<NetworkError>(
+        extraBufferCapacity = 1,
+        onBufferOverflow = BufferOverflow.DROP_OLDEST
+    )
     val networkError = _networkError.asSharedFlow()
app/src/main/java/com/flint/data/di/interceptor/NetworkErrorInterceptor.kt (2)

14-17: CoroutineScope 생명주기 관리 필요

CoroutineScope가 클래스 내부에서 생성되지만 취소되지 않습니다. 이 인터셉터는 Singleton으로 유지되므로 큰 문제는 아니지만, 구조적으로 scope 관리가 없는 fire-and-forget 패턴입니다.

만약 emit 실패 시 에러 핸들링이 필요하다면, launchCoroutineExceptionHandler를 추가하는 것을 고려하세요.


26-37: 3xx 리다이렉트 응답도 에러로 처리됨

300..599 범위에는 3xx 리다이렉트 응답도 포함됩니다. OkHttp는 기본적으로 리다이렉트를 자동 처리하므로 일반적으로 3xx 응답이 여기까지 도달하지 않지만, 리다이렉트가 비활성화된 경우 사용자에게 불필요한 에러 모달이 표시될 수 있습니다.

♻️ 4xx, 5xx만 에러로 처리하는 것이 일반적
             if (!response.isSuccessful) {
                 when (response.code) {
-                    in 300..599 -> {
+                    in 400..599 -> {
                         scope.launch {
                             networkErrorManager.emitError(
app/src/main/java/com/flint/presentation/MainActivity.kt (1)

17-18: 아키텍처 위반: Presentation 레이어에서 Data 레이어 직접 참조

NetworkErrorNetworkErrorManagercom.flint.data.di.interceptor 패키지에 있어 presentation 레이어에서 data 레이어를 직접 참조합니다. Google의 Android App Architecture 권장사항에 따르면, domain 레이어를 통해 간접 참조하는 것이 좋습니다.

Based on learnings, 이 저장소는 Google의 Android App Architecture를 따르고 있습니다.

Comment on lines +49 to +60
networkError?.let { error ->
OneButtonModal(
icon = R.drawable.ic_gradient_none,
title = "문제가 발생했어요",
message = "잠시 후 다시 시도해주세요",
buttonText = "다시 시작하기",
onConfirm = {
restartApplication()
},
onDismiss = {}
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

모달을 닫을 수 없음 & 에러 상태 미초기화

  1. onDismiss = {}가 빈 람다여서 사용자가 모달 외부를 탭해도 아무 동작이 없습니다.
  2. 에러가 발생하면 networkError 상태가 초기화되지 않아 모달이 계속 표시됩니다.
🐛 에러 상태 초기화 추가
                 networkError?.let { error ->
                     OneButtonModal(
                         icon = R.drawable.ic_gradient_none,
                         title = "문제가 발생했어요",
                         message = "잠시 후 다시 시도해주세요",
                         buttonText = "다시 시작하기",
                         onConfirm = {
                             restartApplication()
                         },
-                        onDismiss = {}
+                        onDismiss = { networkError = null }
                     )
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
networkError?.let { error ->
OneButtonModal(
icon = R.drawable.ic_gradient_none,
title = "문제가 발생했어요",
message = "잠시 후 다시 시도해주세요",
buttonText = "다시 시작하기",
onConfirm = {
restartApplication()
},
onDismiss = {}
)
}
networkError?.let { error ->
OneButtonModal(
icon = R.drawable.ic_gradient_none,
title = "문제가 발생했어요",
message = "잠시 후 다시 시도해주세요",
buttonText = "다시 시작하기",
onConfirm = {
restartApplication()
},
onDismiss = { networkError = null }
)
}
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/MainActivity.kt` around lines 49 -
60, The modal cannot be dismissed and the error state isn't cleared because
onDismiss is a no-op and networkError remains set; update the OneButtonModal
invocation so onDismiss clears the error state (e.g., call the state setter to
set networkError to null/empty) and also ensure the onConfirm
(restartApplication) clears the same networkError before/after calling
restartApplication; reference the networkError state variable and the
OneButtonModal usage and modify both onDismiss and the onConfirm callback to
reset the error state.

Comment on lines +65 to +72
private fun restartApplication() {
val packageManager: PackageManager = this.packageManager
val intent = packageManager.getLaunchIntentForPackage(this.packageName)
val componentName = intent!!.component
val mainIntent = Intent.makeRestartActivityTask(componentName)
this.startActivity(mainIntent)
exitProcess(0)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Null 안전성 및 강제 종료 방식 검토

  1. intent!!getLaunchIntentForPackage가 null을 반환할 경우 NPE를 발생시킵니다.
  2. exitProcess(0)는 앱을 강제 종료하여 저장되지 않은 데이터가 유실될 수 있습니다.
🐛 안전한 재시작 로직
     private fun restartApplication() {
         val packageManager: PackageManager = this.packageManager
         val intent = packageManager.getLaunchIntentForPackage(this.packageName)
-        val componentName = intent!!.component
-        val mainIntent = Intent.makeRestartActivityTask(componentName)
-        this.startActivity(mainIntent)
-        exitProcess(0)
+        intent?.let {
+            val componentName = it.component
+            val mainIntent = Intent.makeRestartActivityTask(componentName)
+            this.startActivity(mainIntent)
+            Runtime.getRuntime().exit(0)
+        } ?: run {
+            // Fallback: just finish and let system restart
+            finishAffinity()
+        }
     }
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/MainActivity.kt` around lines 65 -
72, In restartApplication, avoid the unsafe intent!! and abrupt exitProcess(0):
first retrieve the launch intent via
PackageManager.getLaunchIntentForPackage(this.packageName) into a nullable
variable and bail with a logged warning or fallback if null instead of
force-unwrapping; then build the restart intent using
Intent.makeRestartActivityTask(component) (or create a new Intent(this,
MainActivity::class.java) with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK
if component is unavailable) and call startActivity(restartIntent) followed by
finishAffinity() (or finish() where appropriate) to close activities gracefully,
removing the exitProcess call; update references in restartApplication to use
these safer flows.

Copy link
Contributor

@nahy-512 nahy-512 left a comment

Choose a reason for hiding this comment

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

굿! 고생하셨습니다

@nahy-512 nahy-512 merged commit 6081250 into develop Jan 22, 2026
2 checks passed
@nahy-512 nahy-512 deleted the feat/#172-error-modal branch January 22, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feat ✨ 신규 기능을 추가하거나 기존 기능의 동작, 정책을 변경

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] Error Modal

2 participants

Comments