Skip to content

refactor(setting): 남은 이벤트 스트림 2곳을 UiState·MVI로 전환하고 재유입을 막는다 #1502

Closed

Description

@OnlyReadOneBook

작업 유형

refactor — 동작 변경 없는 구조·책임·경계 개선

주 담당 모듈

setting — 설정

개요

설정의 남은 이벤트 스트림을 UiState의 nullable 신호로 옮기고, 해당 ViewModel에 MVI 계약을 함께 적용한다. Channel 제거 뒤 같은 상태 처리를 다시 MVI로 옮기는 재작성을 없앤다.

ProfileEdit, ReceiverEdit, DeliveryCondition과 대응 화면은 후속 저장·사진 변경의 독립 선행인 #2179로 분리했다. 이 이슈에는 다음 2개 ViewModel과 대응 화면만 남긴다.

  • ConnectedAccountsViewModel / ConnectedAccountsScreen
  • PushNotificationViewModel / PushNotificationScreen·NotificationSettingScreen

ReceiverRegisterViewModel / ReceiverRegisterScreen은 완료 조건에서 뺐다. 교체 대상 receiver-register-post로 #944 전환이 가져간다(PM 폼 값·이메일 등록 버튼 결정 대기). 그때까지 기존 _events 선언은 Konsist 가드의 기준선으로만 인정한다.

2026-09-27 develop 6e7e08a 기준 PushNotificationViewModel은 ViewModel과 MutableStateFlow·MutableSharedFlow를 사용한다. #2033에서 옮긴 app 알림 권한 ViewModel과 별개이므로 이 대상은 아직 남는다.

구현 계약:

  • MviViewModel<Intent, UiState, ReducerEvent>를 상속하고 공개 입력을 onIntent로 모은다.
  • 비동기 결과는 dispatch로 전달하며 상태 전이는 순수 reduce에서만 수행한다.
  • UI 신호는 nullable UiState 필드에 두고 ObserveSignal 및 Consume Intent로 소비한다. 늦은 소비가 더 새로운 다른 신호를 지우지 않게 한다.
  • 인증·스낵바 작업은 신호 소비 때문에 취소되지 않도록 화면 코루틴에서 순차 처리한다.
  • 저장 중 중복 제출과 성공·실패·재시도·취소를 보존한다.
  • 마케팅 동의 실패의 값 롤백과 진단을 유지한다. 기존 푸시 알림 토글 저장 실패가 안내 없이 값만 되돌림 #558 계약처럼 STARTED 밖 안내를 재진입에 재생하지 않도록 활성·비활성 Intent로 표현한다.
  • 미전환 MVI 허용 목록의 VM도 새 이벤트용 Channel/MutableSharedFlow를 추가하지 못하도록 Konsist 가드를 확장한다.

완료 조건:

  • 위 2개 VM/화면에 MVI와 UiState 신호 소비가 적용된다.
  • 위 2개 VM의 이벤트 전달용 Channel/MutableSharedFlow가 제거된다.
  • 2개 VM이 MVI 미전환 allowlist에서 빠지고 신규 이벤트 스트림을 가드가 차단한다.
  • 기존 화면·상태 테스트와 신호 유지/소비·연속 실패·중복 저장 회귀 검증 및 앱 컴파일을 통과한다.

참고

Activity

  1. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    on Aug 29, 2026
  2. OnlyReadOneBook commented on Sep 3, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    MVI 전환(#1811)과 방향이 충돌해 blocked_by #1800 을 걸었습니다. 착수 전에 판정이 필요합니다.

    이 이슈는 #228 을 근거로 «ViewModel 이벤트를 Channel 로 쏘지 않고 UI state 에 흡수» 하고, 재유입을 konsist 로 막자고 합니다. 그런데 #1800 이 도입하려는 MviViewModel 베이스는 Channel<MviEffect> 를 일회성 이벤트의 정본으로 삼습니다.

    private val _effect = Channel<F>(Channel.BUFFERED)
    val effect: Flow<F> = _effect.receiveAsFlow()

    지금 순서대로 하면 이렇게 됩니다.

    1. setting 의 Channel 5곳을 UI state 흡수로 고친다
    2. konsist 로 «presentation ViewModel 은 Channel 금지» 를 못박는다
    3. refactor(setting): 수신자 목록 ViewModel의 MVI 전환 #1805(setting MVI 전환)에서 다시 Channel(effect)로 되돌린다
    4. 2에서 만든 konsist 규칙이 MviViewModel 상속체를 전부 막는다

    두 규약이 같은 문제(구성 변경·프로세스 사망 중 이벤트 유실)를 다른 방식으로 풉니다. 어느 쪽을 정본으로 삼을지는 #1800 이 착수 조건으로 판정합니다 — 그 이슈 본문에 판정 항목과 두 선택지의 귀결을 실어 뒀습니다.

    • refactor: ViewModel 이벤트 → UI state 흡수 마이그레이션 (전수 9개 VM) #228 을 유지하면 — MviViewModel 에서 effect 채널을 빼고 일회성 이벤트를 UiState 필드로 모델링합니다. 이 이슈의 마이그레이션 5건은 그대로 유효합니다.
    • MVI 쪽을 택하면 — 마이그레이션 5건이 불필요해지고, konsist 규칙은 «Channel 금지» 가 아니라 «MviViewModel 밖에서 직접 Channel 선언 금지» 로 바뀝니다.

    어느 쪽이든 #1800 판정 전에 이 이슈를 진행하면 산출물이 재작성됩니다.

  3. OnlyReadOneBook commented on Sep 3, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    앞 코멘트를 정정합니다. 충돌이 아닙니다 — blocked_by #1800 을 해제하고, 반대로 #1805(setting MVI 전환)가 이 이슈를 기다리도록 걸었습니다.

    MVI 가 요구하는 것은 「일회성 효과를 상태 전이에서 분리한다」까지고, 그것을 Channel 로 나르는 것은 RuleUp 구현의 선택이지 아키텍처 계약이 아닙니다. 전달 수단은 이 레포 규약을 따르면 됩니다.

    그래서 #1800 의 베이스에서 MviEffect 타입 파라미터를 뺐습니다 — 4타입이 아니라 3타입입니다.

    abstract class MviViewModel<I : MviIntent, S : UiState, E : ReducerEvent>(initialState: S) : ViewModel() {
        val uiState: StateFlow<S>
        abstract fun onIntent(intent: I)
        protected abstract fun reduce(state: S, event: E): S
        protected fun dispatch(event: E)
    }

    일회성 신호는 이 이슈가 정리하는 대로 UiState 의 nullable 필드에 두고(#228 규약, AfternoteDetailUiState.Success.deleteResult 선례), 소비만 Intent.ConsumeXxx 로 들어옵니다. onXxxConsumed() 를 public fun 으로 따로 노출하지 않는다는 것만 달라집니다.

    이 이슈의 작업 5건은 그대로 유효하고, 오히려 먼저 끝나 있는 편이 #1805 전환이 쉽습니다. konsist 규칙도 그대로 세우시면 됩니다 — MVI 베이스는 Channel 을 쓰지 않으므로 걸리지 않습니다. 다만 #1801 이 세울 「MviViewModel 상속체는 MutableStateFlow·Channel 을 선언하지 않는다」 규칙과 겹치므로, 두 규칙은 하나로 합치는 편이 낫습니다 — 따로 두면 나중에 서로를 덮습니다.

  4. changed the title [-]refactor(setting): ViewModel 이벤트 Channel 5곳이 UI state 흡수 규약(#228) 밖에 남아 있다 — 재유입 차단 규칙 포함[/-] [+]refactor(setting): 이벤트 스트림 6곳을 UiState·MVI로 전환하고 재유입을 막는다[/+] on Sep 8, 2026
  5. 12 remaining items

  6. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 28, 2026
  7. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    이 이슈를 맡은 PR 은 #2211 다 (base refactor/963-current-push-save-reporting).

    base 가 기본 브랜치(develop)가 아니라서 GitHub 은 이 이슈의 Development 칸에
    아무것도 표시하지 않는다. 본문에 Closes #N 이 있어도 마찬가지다 — 그 링크는 기본 브랜치를
    target 하는 PR 에서만 만들어진다.

    PR 이 닫히거나 머지되면 이 이슈의 pr-open 라벨은 다음 리컨사일에서 떨어진다.

  8. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:setting설정 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions