docs(dm): creatorId 진입 crash 수정을 기록한다

This commit is contained in:
2026-06-17 13:52:49 +09:00
parent 3dacc50e1b
commit 01afed89f8
2 changed files with 73 additions and 0 deletions

View File

@@ -443,6 +443,60 @@
- 화면 이탈 또는 앱 background 전환 시 disconnect API가 호출된다.
- SSE 연결 실패가 앱 crash로 이어지지 않는다.
### Phase 8: 크리에이터 채널 DM 진입 crash 수정
- [x] **Task 8.1: creatorId 기반 진입 thread crash 재현 테스트 추가**
- Files:
- Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt`
- 작업:
- `DmChatRoomActivity.newIntentByCreatorId()`로 들어오는 흐름에 대응해 `enter(roomId = 0L, creatorId > 0L)` 테스트를 보강한다.
- `CreateOrGetRoom` 이후 `OpenRoom` 결과가 background scheduler에서 전달되어도 `LiveData` 상태 갱신이 main thread에서 처리되어야 함을 고정한다.
- 기존 roomId 기반 진입 테스트는 유지하고, creatorId 기반 진입만의 Rx chain thread 전환 문제를 분리해 검증한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`
- Expected: 수정 전에는 background thread `MutableLiveData.setValue()` 예외 또는 main thread 보장 assertion으로 RED를 확인하고, 수정 후 PASS한다.
- 검증 기록:
- 2026-06-17: `DmChatRoomViewModelTest``creatorId 진입은 openRoom 결과 처리 전에 main thread로 다시 전환한다` 테스트를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1` 실행 결과 해당 테스트가 `DmChatRoomViewModelTest.kt:126` assertion failure로 RED가 되었고, 현재 `createRoomAndOpen()`에는 `flatMap` 이후 `OpenRoom` 결과 처리 전 main thread 재전환이 없음을 확인했다.
- [x] **Task 8.2: createRoomAndOpen main thread 전환 보장**
- Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt`
- 작업:
- `createRoomAndOpen()``CreateOrGetRoom``OpenRoom` 연속 호출 흐름에서 최종 결과 처리 전에 main thread 전환이 보장되는지 점검한다.
- `handleOpenRoomResult()`, `handleError()`, `_roomOpenedEventLiveData` 갱신, `emitContent()` 호출이 main thread에서 실행되도록 최소 변경을 적용한다.
- `openRoom(roomId)` 단독 진입, pagination, send/retry, SSE callback scheduling, reconnect/disconnect 정리 정책은 변경하지 않는다.
- `postValue()`로 증상을 숨기기보다 기존 ViewModel의 `setValue` 기반 동기 상태 갱신 의미를 유지할 수 있는 scheduler 위치를 우선 검토한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`
- Expected: creatorId 기반 진입에서 `Cannot invoke setValue on a background thread` 예외 없이 Content 상태와 room opened event가 발행된다.
- 검증 기록:
- 2026-06-17: `DmChatRoomViewModel.createRoomAndOpen()``flatMap` 뒤에 `observeOn(AndroidSchedulers.mainThread())`를 추가해 `OpenRoom` 결과 처리 전 main thread 전환을 보장했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`을 실행해 Task 8.1에서 RED였던 테스트를 포함한 `DmChatRoomViewModelTest` 전체 PASS를 확인했다.
- [x] **Task 8.3: DM 채팅 회귀 테스트와 빌드 확인**
- Files:
- Check: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/*Test.kt`
- Check: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt`
- 작업:
- DM 채팅 ViewModel 변경이 mapper, repository, SSE parser/client, pagination 상태 테스트를 깨지 않는지 확인한다.
- 가능하면 debug 빌드와 ktlint를 순차 실행해 Gradle cache 경합을 피한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`
- Run: `./gradlew :app:compileDebugKotlin --max-workers=1`
- Run: `./gradlew :app:ktlintCheck --max-workers=1`
- Expected: 모두 PASS. 기존 `.editorconfig` `disabled_rules` deprecation warning은 실패로 보지 않는다.
- 검증 기록:
- 2026-06-17: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`를 순차 실행해 모두 PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig``disabled_rules` deprecation warning이 출력됐지만 실패는 없었다.
- [x] **Task 8.4: 수동 확인 항목 갱신**
- Files:
- Check: `DmChatRoomActivity`
- Check: `CreatorChannelActivity`
- 확인 항목:
- 크리에이터 채널에서 `DM 보내기`를 터치하면 `DmChatRoomActivity`로 이동한다.
- `creatorId` 기반 진입 후 방 생성/조회와 OpenRoom 결과 반영 중 앱이 crash 되지 않는다.
- header 상대 정보와 초기 메시지 목록이 표시된다.
- 채팅 탭의 기존 `roomId` 기반 DM 진입은 기존처럼 동작한다.
## 5. 검증 기록
- 2026-06-10: `docs/20260610_DM_채팅화면/prd.md`를 확인해 DM 채팅방 진입, UI 제거 대상, REST API, SSE 이벤트, pagination, 전송 실패/재시도, lifecycle disconnect 요구사항을 계획에 반영했다.
- 2026-06-10: `docs/agent-guides/work-plan-docs.md`, `docs/agent-guides/build-test-style.md`, `docs/agent-guides/code-style.md`를 확인해 신규 계획 문서 위치, phase/task 체크박스 형식, 테스트 명령 작성 방식을 확인했다.

View File

@@ -11,6 +11,7 @@
- DM 채팅은 AI 채팅과 다르게 크리에이터와 사용자 간 메시지 송수신, SSE 실시간 이벤트 연결/해제, 커서 기반 과거 메시지 조회가 핵심이다.
- 화면 이탈 또는 앱 백그라운드 전환 시 실시간 연결 해제 API를 항상 호출해야 하므로 생명주기 요구사항을 명확히 문서화해야 한다.
- REST pagination과 SSE 실시간 수신 결과가 겹칠 수 있으므로 메시지 병합/중복 제거 기준이 필요하다.
- 크리에이터 채널에서 `DM 보내기`를 눌러 `creatorId` 기반으로 `DmChatRoomActivity`에 진입하면 `DmChatRoomViewModel.emitContent()`가 background thread에서 `MutableLiveData.setValue()`를 호출해 앱이 crash 된다.
---
@@ -22,6 +23,7 @@
- 텍스트 메시지 전송 후 서버 응답 메시지를 화면에 반영한다.
- 채팅방 화면 진입/이탈, 앱 foreground/background 전환에 따른 SSE 연결/해제 정책을 정의한다.
- SSE 이벤트 이름/응답 payload, 재연결 가이드, UI thread 비차단, 최신 메시지 동기화 같은 실시간 연결 운영 기준을 정의한다.
- 크리에이터 채널 `DM 보내기` 진입에서 방 생성/열기 완료 후 모든 `LiveData` 상태 갱신이 main thread에서 수행되어 background thread `setValue()` 예외가 발생하지 않도록 한다.
---
@@ -133,6 +135,20 @@ data class UserCreatorChatRoomOpenResponse(
)
```
### Creator Channel DM Entry Crash Fix
크리에이터 채널의 `DM 보내기` 버튼에서 DM 채팅방으로 이동할 때 앱이 종료되지 않도록 한다.
#### Requirements
- `DmChatRoomActivity.newIntentByCreatorId(context, creatorId)`로 진입한 경우 `CreateOrGetRoom` 성공 후 `OpenRoom` 결과를 안전하게 처리한다.
- `DmChatRoomViewModel.emitContent()`에서 `MutableLiveData.setValue()`를 호출하는 시점은 main thread여야 한다.
- RxJava chain에서 `flatMap` 이후 upstream/downstream scheduler가 달라져도 `handleOpenRoomResult()`, `handleError()`, `_roomOpenedEventLiveData` 갱신은 main thread에서 실행되어야 한다.
- 기존 `roomId` 기반 채팅 탭 DM 진입 동작은 변경하지 않는다.
- 수정은 DM 채팅 ViewModel의 thread 전환 문제에 한정하고, 크리에이터 채널 layout이나 다른 UI 동작은 이번 범위에서 변경하지 않는다.
#### Edge Cases
- `CreateOrGetRoom`은 성공했지만 `OpenRoom`이 실패해도 앱 crash 없이 기존 오류 처리 정책을 따른다.
- 빠르게 화면을 이탈하거나 background 전환이 발생해도 예약된 realtime callback 정리 정책을 훼손하지 않는다.
### SSE Realtime Events
채팅방이 열려 있는 동안 서버 이벤트를 연결해 새 메시지를 실시간으로 반영한다.
@@ -302,6 +318,7 @@ data class UserCreatorChatMessageItemDto(
- 앱 foreground/background 감지는 Activity lifecycle과 앱 전체 `ProcessLifecycleOwner` 중 어떤 기준을 사용할지 구현 계획에서 확정한다.
- 구현 전 `docs/20260610_DM_채팅화면/plan-task.md`를 작성하고, 그 문서에 따라 최소 구현한다.
- 테스트는 DTO mapper, 메시지 정렬/중복 제거, pagination state, disconnect lifecycle을 우선 검증한다.
- 크리에이터 채널 `DM 보내기` crash 수정은 기존 DM 채팅 문서의 후속 범위로 누적하며, 구현 전 `plan-task.md`에 대응 task와 검증 기록을 추가한다.
---
@@ -331,6 +348,7 @@ data class UserCreatorChatMessageItemDto(
- SSE 재연결 후 필요 시 GetMessages API로 누락 메시지를 동기화한다.
- disconnect 처리는 UI thread를 블로킹하지 않는다.
- 제거 대상 UI(`character_type_badge`, `ll_can_badge`, `iv_more`, `notice_container`)가 DM 화면에 나타나지 않는다.
- 크리에이터 채널 `DM 보내기``creatorId` 기반 진입 시 `Cannot invoke setValue on a background thread` 예외 없이 DM 채팅방 Content 상태가 표시된다.
---
@@ -365,3 +383,4 @@ data class UserCreatorChatMessageItemDto(
- 2026-06-10: 사용자 제공 백엔드 계약을 반영해 SSE 이벤트 이름과 payload를 확정했다. `connected` 이벤트는 `"connected"` 문자열 handshake로, `message` 이벤트는 `UserCreatorChatMessageItemDto` JSON으로 문서화했다. 서버 `reconnectTime=3000`ms, `Last-Event-ID` 기반 replay 미지원, 재연결 후 GetMessages API를 통한 누락 메시지 보정 요구사항도 반영했다.
- 2026-06-10: 백그라운드 조사 결과와 대조해 `ConnectEvents` 응답 표기를 `ApiResponse<Unit>`에서 `text/event-stream`으로 보정하고, API Summary와 기술 제약도 SSE stream 수신/파싱 기준으로 갱신했다.
- 2026-06-10: 후속 Repository 구현 시 `Authorization` 헤더 오입력 방지를 위해 단일 bearer helper로 헤더 문자열을 생성하도록 기술 제약에 기록했다.
- 2026-06-17: 사용자 제보 스택트레이스(`DmChatRoomViewModel.emitContent()``MutableLiveData.setValue()` background thread 예외)와 `DmChatRoomViewModel.kt``createRoomAndOpen()` Rx chain을 확인했다. 크리에이터 채널 `DM 보내기``creatorId` 기반 진입에서 `CreateOrGetRoom``OpenRoom` 결과 처리 thread를 main thread로 보장해야 하는 요구사항을 기존 DM 채팅화면 PRD에 후속 범위로 누적했다.