Skip to content

Feat/440 선물 등록 시 사전 질문 작성#446

Open
HamBP wants to merge 26 commits intodevelopfrom
feat/440
Open

Feat/440 선물 등록 시 사전 질문 작성#446
HamBP wants to merge 26 commits intodevelopfrom
feat/440

Conversation

@HamBP
Copy link
Member

@HamBP HamBP commented Mar 4, 2026

Issue

작업 내용

  • 선물 받기 시 사전 질문이 있을 경우 사전 질문을 작성한다.
  • 홈 -> 선물 사전 질문 작성 화면으로 넘어가면서 동일한 API 두 개(선물 정보 조회, 사전 질문 목록 조회)를 중복 호출하게 되는데, 이를 방지하기 위해 간단한 캐싱 로직 작성.

BE 논의 내용

  • 기존 선물 API에 공연 정보 추가
  • API 호출 순서 변경 요청 받음.
    • 기존: 사전 질문 등록 -> 선물 받기
    • 변경: 선물 받기 -> 사전 질문 등록 (권한 체크 위함)
    • 선물 받기 API만 성공했을 경우 질문이 누락된 상태로 티켓이 존재. 약간의 대비로 실패 시 리트라이 및 파베 로그 전송 로직만 작성해 두었음.

@HamBP HamBP self-assigned this Mar 4, 2026
@HamBP HamBP added the feat 새로운 기능 label Mar 4, 2026
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Test Results

12 files  12 suites   0s ⏱️
 9 tests  9 ✅ 0 💤 0 ❌
20 runs  20 ✅ 0 💤 0 ❌

Results for commit 7a93107.

♻️ This comment has been updated with latest results.

Copy link
Member

@mangbaam mangbaam left a comment

Choose a reason for hiding this comment

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

Recommended Action

  1. [필수] putPreQuestionAnswer()의 unsafe cast → safe cast 수정 (크래시 방지)
  2. [필수] receiveGift()에서 recordExceptionHandler 대신 explicit try-catch로 변경하여 실패 이벤트 전송
  3. [필수] fetchGiftAndPreQuestions() 실패 시 Error 상태 전환 + Loading UI 추가
  4. [중요] 선물 등록 성공 + 사전질문 실패를 구분하는 별도 이벤트/UX 검토
  5. [중요] cachedUser null 시 에러 피드백 추가
  6. [권장] receiveGift() 성공 후 캐시 무효화

.retry(2)
.catch { throwable ->
FirebaseCrashlytics.getInstance().recordException(throwable)
_events.send(GiftPreQuestionEvent.GiftRegistrationFailed)
Copy link
Member

Choose a reason for hiding this comment

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

선물 받기 API 성공 → 사전질문 제출 실패 시 GiftRegistrationFailed 이벤트가 발생합니다. 사용자는 "등록 실패"를 보지만, 실제로 서버에는 선물이 이미 등록된 상태입니다. 사전질문 답변만 누락된 상태로 티켓이 존재하게 됩니다.

또한 .catch가 CancellationException도 잡아서 structured concurrency를 위반합니다.

Copy link
Member

Choose a reason for hiding this comment

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

권장:

  • PreQuestionSubmitFailed 같은 별도 이벤트를 만들어 사전질문 수정 화면으로 안내
  • .retry(2) { it !is CancellationException } 조건 추가
  • .catch에서 CancellationException은 rethrow


private fun fetchGiftAndPreQuestions() {
viewModelScope.launch(recordExceptionHandler) {
_uiState.update { GiftPreQuestionUiState.Loading }
Copy link
Member

Choose a reason for hiding this comment

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

fetchGiftAndPreQuestions() - 실패 시 무한 로딩

GiftPreQuestionViewModel.kt

recordExceptionHandler가 예외를 잡으면 UI 상태가 Loading에 머물러 사용자는 빈 화면만 보게 됩니다. 에러 상태도 없고 재시도 버튼도 없습니다.

Copy link
Member

Choose a reason for hiding this comment

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

권장:

  • GiftPreQuestionUiState.Error 상태 추가
  • 명시적 try-catch로 에러 상태 전환 및 재시도 UI 제공


val senderId = gift.senderUserId
val hasPreQuestion = ticketingRepository.getPreQuestions(gift.showId).first().isNotEmpty()
val myUserId = authRepository.cachedUser.first()?.id ?: return@launch
Copy link
Member

Choose a reason for hiding this comment

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

authRepository.cachedUser.first()?.id ?: return@launch로 cachedUser가 null이면 아무 피드백 없이 종료됩니다. 딥링크로 진입한 사용자가 아무 반응 없이 막히게 됩니다.

Copy link
Member

Choose a reason for hiding this comment

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

권장: GiftStatus.FAILED 이벤트를 보내서 에러 다이얼로그 표시


fun putPreQuestionAnswer(questionId: Long, answer: String) {
_uiState.update {
val state = it as GiftPreQuestionUiState.Success
Copy link
Member

Choose a reason for hiding this comment

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

val state = it as GiftPreQuestionUiState.Success // unsafe cast!
receiveGift()에서는 as?를 사용하는데 여기만 as를 사용합니다. Loading 상태에서 호출되면 ClassCastException으로 앱이 크래시합니다.

수정: as? ... ?: return@update it 패턴으로 변경


pendingGift = null

giftRepository.receiveGift(giftUuid)
Copy link
Member

Choose a reason for hiding this comment

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

giftRepository.receiveGift() 자체가 예외를 던지면 recordExceptionHandler가 잡아서 Crashlytics에만 기록합니다. GiftRegistrationFailed 이벤트가 전송되지 않아 사용자에게 아무 피드백이 없습니다.

권장: recordExceptionHandler 대신 명시적 try-catch로 실패 이벤트 전송

) {
val state = uiState

if (state is GiftPreQuestionUiState.Success) {
Copy link
Member

Choose a reason for hiding this comment

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

Success 상태만 렌더링하고, Loading 시 빈 화면이 표시됩니다. 로딩 인디케이터도 에러 UI도 없습니다.

) : GiftRepository {
private data class CacheEntry<T>(val value: T, val cachedAt: Long)

private val giftCache = ConcurrentHashMap<String, CacheEntry<Gift>>()
Copy link
Member

Choose a reason for hiding this comment

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

receiveGift() 후 캐시 무효화 없음

선물 등록 후에도 60초간 이전 상태의 캐시를 반환합니다. receiveGift() 성공 시 giftCache.remove(giftUuid) 추가를 권장합니다

}

private fun processGiftWhenLoggedIn(giftUuid: String) {
viewModelScope.launch(recordExceptionHandler) {
Copy link
Member

Choose a reason for hiding this comment

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

recordExceptionHandler로 모든 에러 무시

선물 정보/사전질문 조회 실패 시 다이얼로그가 안 뜹니다. 사용자는 딥링크 후 아무 반응이 없습니다.

val deepLink = intent.data.toString()
intent.data = null
val regex = "^boolti://gift/([\\w-])+$".toRegex()
if (regex.matches(deepLink)) {
Copy link
Member

Choose a reason for hiding this comment

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

딥링크 regex 미매칭 시 로깅 없음

HomeScreen.kt에서 boolti://gift/ 패턴 미매칭 시 사일런트 드롭됩니다. 디버깅이 어렵습니다.

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.

선물하기 등록 시 사전 질문 작성 폼

2 participants