Skip to content

fix(setting): 오류 상태 처리를 안정화한다 (#708) - #1925

Closed
OnlyReadOneBook wants to merge 27 commits into
refactor/1502-setting-state-mvifrom
handoff/708-setting-1hyok
Closed

OnlyReadOneBook wants to merge 27 commits into
refactor/1502-setting-state-mvifrom
handoff/708-setting-1hyok

Conversation

@OnlyReadOneBook

@OnlyReadOneBook OnlyReadOneBook commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator

작업 내용

  • 설정 홈, 프로필 수정, 연결 계정, 푸시 알림에서 초기 조회 실패를 로딩·오류 상태와 재시도로 표시합니다.

  • 중복 로드를 막는 job 가드와 runCatchingCancellable을 적용하고 계정 연결·해제 실패를 UiState 신호로 전달하고 ConsumeEvent Intent로 소비합니다.

  • 분리된 ConnectedAccountsContent·PushNotificationContent에 오류 화면 분기를 연결하고 ViewModel 회귀 테스트와 푸시 알림 스크린샷을 포함합니다. 수신자 목록은 develop의 공용 선택 화면 구조를 따릅니다.

  • #1967의 6개 설정 ViewModel MVI 계약을 실제 부모로 사용합니다. 오류 처리에서 새 Channel이나 별도 public 입력 함수를 도입하지 않습니다.

  • 이 PR이 함께 수정하는 SettingViewModel도 MVI로 전환해 프로필·로그아웃·탈퇴 상태를 하나의 UiState에 모았습니다. #1805가 같은 소비처를 다시 작성할 범위를 제거합니다.

연결 이슈

설정 담당 이관

원본 PR #1709의 설정 작업을 1hyok 명의로 이어받습니다. 인수 기준 커밋은 be17a0327a670e57f46a3a4f3b63ebbc3c7b8e12이며 기존 커밋의 작성 이력은 보존합니다. 원본의 승인·변경 요청은 새 PR로 자동 이전되지 않으므로 다시 리뷰받습니다.

기존 리뷰 반영

  • 푸시·연결 계정 조회 오류를 UiText.Resource로 전달하고 화면이 그 값을 표시합니다. 설정 홈의 프로필 오류는 SettingProfileState.Error 에서 message 를 없애고 화면이 setting_profile_load_error 리소스를 그립니다. 같은 문장이 코드와 리소스 두 곳에 있던 것을 리소스 하나로 모았습니다. 사용되지 않는 setting_receivers_load_error는 제거했습니다.
  • develop에 반영된 #1924의 서비스 알림 저장 실패 안내 위에 조회 오류·재시도를 통합합니다. 마케팅 동의 및 저장 실패·재시도 동작을 보존했습니다.
  • 마케팅 동의/기기 알림 설정을 하위 서비스 알림 화면에 다시 중복 표시하던 병합 충돌을 해소했습니다. 정상 상태의 토글 세 개는 Column으로 세로 배치합니다.

원본 변경 요청을 반영했습니다.

검증

CI Test Plan

{
  "androidTest": {
    "mode": "full",
    "reason": "설정 메인·탈퇴의 aggregate UiState 소비를 SettingNavHost에 연결하므로 전체 계측 lane을 실행한다. 조회 오류·재시도 및 상태 소비는 Robolectric 회귀와 SettingImplementedCoverageAndroidTest의 실제 설정 host 경로로 검증한다. 앱 main/unit/androidTest Kotlin 컴파일을 확인했다.",
    "tests": []
  }
}

확정된 설정 계약 통합

#1967에서 #1691·#1739·#1429를 함께 구현한 최종 계약을 실제 부모로 통합했습니다. 이 PR의 소비자와 회귀 fixture는 좁은 프로필·수신자 계약 및 설정 소유 계정·알림 계약을 직접 사용하며, 저장 정책은 두 UseCase를 사용합니다. 별도 후속 저장소·UseCase 재작성은 예약하지 않습니다.

최신 통합 검증: 설정 presentation 82개 테스트 통과(실패·오류 0), 앱 main/unit/androidTest Kotlin 컴파일 및 setting ktlint 통과. #1962의 실제 Nav3 SavedState serializer 회귀도 포함합니다. 화면 배치는 변경하지 않았습니다.

공용 텍스트 타입 제거 반영

독립 PR #2006 (#1887)의 미사용 UiText.DynamicOrResource 제거를 반영하고, 연결 계정의 문자열 변환에서 해당 타입 분기를 함께 삭제했습니다. 기존 Resource·Dynamic 표시는 유지됩니다. 실제 base와 기존 설정 기능 범위는 유지합니다.

검증: 현재 HEAD에서 설정 오류 회귀 9개, 앱 debug Kotlin 컴파일, 설정·코어 ktlint 통과.

koongmai and others added 6 commits August 28, 2026 15:47
# Conflicts:
#	feature/setting/presentation/build.gradle.kts
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavGraph.kt
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/viewmodel/SettingViewModel.kt
# Conflicts:
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverListScreen.kt
- develop이 #791로 ReceiverListScreen을 공용 ReceiverSelectScreen 소비 구조로 이미 리팩터링했으므로, 이 브랜치가 추가했던 uiState/Loading/Error/retry 확장은 되돌리고 develop 구조에 맞춘다 (ReceiverListScreen.kt, ReceiverListViewModel.kt, SettingNavGraph.kt).
- ConnectedAccountsViewModel의 링크/해제 실패를 Snackbar 이벤트로 전달하도록 바꾼 이 브랜치의 설계에 맞춰, develop이 그 사이 추가한 SettingAccountSecurityTest의 관련 단언을 이벤트 기반으로 갱신한다.
- PushNotificationErrorViewModelTest에 누락된 @config(sdk = [35])를 추가해 로컬/CI Robolectric 실행 오류를 해결한다.
# Conflicts:
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt
#	feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationScreen.kt
@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

열린 PR 중 이 PR 과 같은 파일을 서로 다르게 고친 것이 있다. 어느 쪽이 먼저 머지되든 나중 쪽은 base 를 병합해야 통과한다 (#1750).

머지 자체는 GitHub 이 막는다 — 한쪽이 들어가면 다른 쪽은 CONFLICTING 이 되어 merge queue 에 못 들어간다.
이 라벨은 그 전에 순서를 정하라는 신호다. 먼저 갈 쪽을 정하고, 나중 쪽은 그 머지 뒤에 base 를 병합한다.
상대 PR 에도 같은 안내가 달린다.

이 코멘트는 리컨사일러가 매 실행 현재 상태로 고쳐 쓴다. 해소되면 sibling-conflict 라벨은 자동으로 떨어진다.

@OnlyReadOneBook
OnlyReadOneBook changed the base branch from develop to handoff/558-setting-1hyok September 6, 2026 13:56
@OnlyReadOneBook
OnlyReadOneBook marked this pull request as ready for review September 6, 2026 13:56
@OnlyReadOneBook OnlyReadOneBook added the screenshot-baseline CI 표준 환경에서 PR screenshot baseline 생성·적용 요청 label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

📸 CI 표준 환경에서 screenshot baseline 5개를 생성·검증해 597bc75 로 커밋했습니다.
실행 로그·생성 artifact: https://github.com/Afternote/Afternote-FE/actions/runs/34188744646
이미지 내용은 이 PR의 PNG diff에서 눈으로 최종 확인해 주세요. 필수 검사도 새 SHA에 다시 요청했습니다.

@github-actions github-actions Bot removed the screenshot-baseline CI 표준 환경에서 PR screenshot baseline 생성·적용 요청 label Sep 6, 2026
@OnlyReadOneBook
OnlyReadOneBook marked this pull request as draft September 6, 2026 14:03
@github-actions github-actions Bot added the conflict base 와 충돌 — CI 가 트리거되지 않는다. base 를 병합하면 자동으로 떨어진다 label Sep 12, 2026
@github-actions github-actions Bot removed the conflict base 와 충돌 — CI 가 트리거되지 않는다. base 를 병합하면 자동으로 떨어진다 label Sep 12, 2026
refactor/1502-setting-state-mvi 가 UserRepository 를 좁히면서 core 의
FakeUserRepository 에서 pushSetting·connectedAccounts 와 계정·푸시 콜백을
걷어냈고 소비처도 같이 고쳤다. 그 뒤 이 브랜치로 들어온 머지가 afternote·
mindrecord·timeletter 의 테스트 픽스처만 옛 모양으로 되살려, 좁힌 fake 를
쓰면서 없는 멤버를 참조하는 상태가 됐다.

git 은 충돌로 보지 않는 의미 충돌이라 그대로 스택 위로 번졌다. PR #2067 이
core/ui 를 건드려 afternote·mindrecord·timeletter 가 처음 컴파일 대상에
들어가면서 Unit Test 가 빨개졌고, 그 아래 #1930·#2044 는 변경 파일이 setting
안에 갇혀 있어 해당 모듈을 돌리지 않아 초록으로 보였다.

세 파일을 1502 의 형태로 되돌린다. 프로덕션 변경은 없다.
…ng-1hyok

프로덕션 Preview 제거(#1992)와 설정 메뉴 안내 분리(#2000)를 이 PR 의 오류·로딩 MVI 위에 합친다.

- SettingScreen.kt: 호출부는 양쪽을 모두 남긴다. 이 PR 의 MVI 인자(uiState.profile,
  SettingIntent.Refresh 재시도, SettingIntent.Logout)에 base 의 onUnavailableClick
  스낵바를 더했다. 어느 쪽도 버리지 않았다. 파일 끝 SettingScreenPrev 는 base 의 제거를
  따른다(옛 파라미터 이름을 쓰고 있어 살릴 수도 없다).
- ConnectedAccountsScreen.kt: Preview 는 base 를 따라 지우고, 같은 충돌 덩어리에 있던
  UiText.resolve(Resources) 헬퍼는 남긴다. 리뷰 반영으로 이번 PR 이 새로 쓴 코드이고
  스낵바 코루틴에서 UiText 를 푸는 유일한 경로다.
- ProfileEditScreen.kt: import 충돌에서 Preview 만 지우고 stringResource 는 남긴다.
- PushNotificationContent.kt · WithdrawGuideScreen.kt: Preview 전용 충돌이라 base 를 따른다.

검증: setting presentation testDebugUnitTest·ktlintCheck, app debug main/unit/androidTest
Kotlin 컴파일, konsist test --rerun-tasks, setting screenshotTest 컴파일,
core:ui·receiver·afternote presentation 단위 테스트 모두 통과.
@OnlyReadOneBook

Copy link
Copy Markdown
Collaborator Author

변경 요청 3라운드 6건을 현재 head 7dfcb431a 에서 다시 확인했습니다. 지난 답변의 b9de721 뒤로 base 병합이 네 번 들어왔고 그중 하나가 UiText 표시 경로를 손대서, 병합이 되돌린 것이 없는지 줄 단위로 대조했습니다. 여섯 건 모두 남아 있어 새로 고칠 코드는 없습니다.

1라운드

  • 갱신 중 Loading 되돌림. SettingViewModel.kt:45 의 ProfileLoading 이 if (state.profile is SettingProfileState.Success) state else state.copy(profile = SettingProfileState.Loading) 이라, RESUMED 마다 부르는 refresh() 는 기존 Success 를 유지하고 최초 로드와 오류 재시도에서만 스피너가 섭니다.
  • 오류 화면의 로그아웃. SettingScreen.kt:260-272 의 SettingProfileState.Error 분기가 재시도 본문 아래에 settings_logout 항목과 기존 확인 다이얼로그를 함께 둡니다.
  • 새 로딩 두 곳. ConnectedAccountsContent.kt:59 와 PushNotificationContent.kt:62 가 LoadingBody() 입니다. SettingScreen·ProfileEditScreen·ReceiverEditContent 에 남은 raw CircularProgressIndicator 는 이 PR 이 쓴 줄이 아니라 refactor(timeletter,setting): 로딩 스피너 6곳을 core:ui LoadingBody 로 치환 (#641 분리) #1491 범위로 둡니다.

2라운드

  • SettingProfileState.Error 는 SettingContract.kt:22 에서 message 없는 data object 이고, 화면 두 곳(SettingScreen.kt:263, ProfileEditScreen.kt:123)이 stringResource(R.string.setting_profile_load_error) 를 그립니다.

3라운드

  • 연결·해제 실패 문구. ConnectedAccountsViewModel.kt:106,112 가 setting_connected_accounts_link_error 와 setting_connected_accounts_unlink_error 를 mutate() 에 넘기고 실패는 UiText.Resource(errorResId) 로 나갑니다. 설정 presentation 의 main viewmodel 디렉터리에서 한글 문자열 리터럴은 0건입니다.
  • provider 별 진행 판정. mutationJobs 가 provider 키 맵이라(ConnectedAccountsViewModel.kt:27, 조기 반환은 :122) 같은 provider 연타만 막고 다른 계정 토글은 그대로 받습니다. 회귀는 SettingErrorStateViewModelTest 의 "연결 계정은 한 provider 요청 중에도 다른 provider 조작을 무시하지 않는다" 와 "연결 계정은 같은 provider 요청이 진행 중이면 연타를 한 번만 보낸다" 두 건입니다.

커밋은 전부 origin 에 올라가 있고 이 head 에서 Unit Test, Ktlint, Android Lint, screenshot, Pixel 2 API 30 androidTest 가 통과입니다. 남은 red 는 guard 하나인데 #708 이 아직 열린 #1502 에 blocked 이고 base 가 refactor/1502-setting-state-mvi 라서 나는 스택 순서 판정이라, 선행이 develop 에 들어가면 풀립니다.

@github-actions

Copy link
Copy Markdown
Contributor

스택 연결 조치 필요

#1967 이 머지되지 않은 채 닫힌 시점에 네이티브 스택 #1970 에 남아 이 PR의 스택 병합을 막는 것으로 감지됐습니다.

머지 전에 기존 스택을 해제하고, 필요한 열린 PR만 의도한 순서로 다시 연결해 주세요.

gh stack unstack 1970
gh stack link --base develop <아래 PR> <위 PR>

unstack/relink 자체에는 Actions 이벤트가 없어 이 기록이 남을 수 있습니다. 현재 병합 가능 여부는 merge queue의 live 검사를 기준으로 확인하세요.

@OnlyReadOneBook

Copy link
Copy Markdown
Collaborator Author

PM 결정을 기다리는 항목은 깃허브에 열어 두지 않고 피그마 질문 한 자리에만 둔다(2026-09-18 일혁 결정). 이 PR 은 #1962(설정 Nav3 이관) 위에 쌓여 있고, #1962 는 설정 메인 사후 전달 조건 진입 경로의 PM 답을 기다린다: https://www.figma.com/design/UP9ZR186jHvRBicjA2SOea/?node-id=4327-64430#1923797606

그래서 사슬 13건을 함께 닫는다. 브랜치와 승인 리뷰는 그대로 남아 있고, 답이 오면 그 답을 근거로 #1962 부터 다시 열어 같은 순서로 올린다. 코드가 틀려서 닫는 것이 아니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android-test PR head에서 Android Managed Device 계측 테스트 실행 sibling-conflict 열린 형제 PR 과 같은 파일에서 충돌 — 나중에 머지되는 쪽이 base 를 병합해야 한다. 해소되면 자동으로 떨어진다

Projects

None yet

Development

Successfully merging this pull request may close these issues.

서버 조회 실패가 로그아웃·빈 목록·OFF 기본값으로 위장 — 오류·재시도 상태 미결선

3 participants