fix: release 환경 약관 동의 화면 버튼 눌렀을 때 응답 없음 해결#122
Conversation
WalkthroughUserRepository의 agreeTerms 함수 반환 타입이 Result에서 Result로 변경되었습니다. 이를 위해 TermsAgreementModel 데이터 클래스가 새로 추가되고, TermsAgreementResponse를 TermsAgreementModel로 변환하는 매퍼 함수가 구현되었습니다. DefaultUserRepository의 구현도 이에 맞게 수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant UI
participant UserRepository
participant Service
participant Mapper
UI->>UserRepository: agreeTerms(termsAgreed)
UserRepository->>Service: agreeTerms(termsAgreed)
Service-->>UserRepository: TermsAgreementResponse
UserRepository->>Mapper: toModel(TermsAgreementResponse)
Mapper-->>UserRepository: TermsAgreementModel
UserRepository-->>UI: Result<TermsAgreementModel>
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 변경사항 중 범위를 벗어난 코드 변경은 발견되지 않았습니다.) Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt (2)
3-9: KDoc 추가 제안
해당 모델의 의미(약관 동의 API 응답을 표현)와 사용처(agreeTerms 반환값)를 간단히 문서화하면 가독성과 유지보수성이 좋아집니다.package com.ninecraft.booket.core.model +/** + * 약관 동의 API 성공 시 서버에서 내려주는 사용자 정보를 표현하는 도메인 모델. + * UserRepository.agreeTerms 의 반환 타입으로 사용됩니다. + */ data class TermsAgreementModel( val id: String, val email: String, val nickname: String, val provider: String, val termsAgreed: Boolean, )
3-9: 중복 모델 재사용/정규화 검토
UserProfileModel 과 필드 구성이 동일합니다. 의미적 분리를 위해 별도 타입을 두는 선택도 타당하지만, 중복을 줄이고자 한다면:
- UserProfileModel 재사용, 또는
- 공통 UserBaseModel 추출 후 두 모델이 이를 확장
을 고려할 수 있습니다. 현재는 선택 사항입니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt(1 hunks)core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt(3 hunks)core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt(1 hunks)core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
📚 Learning: 2025-07-09T01:14:29.836Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
Applied to files:
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.ktcore/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (6)
core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt (1)
3-9: LGTM — TermsAgreementModel 추가가 문제의 핵심(Any/Unit 직렬화 문제) 해결에 적합합니다.
모델 정의와 필드 구성이 일관적이며, 레이어 경계(도메인 모델)에도 잘 맞습니다.core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (2)
15-17: LGTM — Retrofit 직렬화 오류(Any/Object/Unit 컨버터 문제) 회피를 위한 반환 타입 전환이 올바르게 반영되었습니다.
서비스 응답을 즉시 toModel()로 변환해 Result을 반환하는 흐름이 Data 레이어의 일관성을 유지합니다.
15-17: ReedService.agreeTerms 반환 타입 및 서버 응답 상태 확인 요청
- agreeTerms 시그니처가
suspend fun agreeTerms(@Body TermsAgreementRequest): TermsAgreementResponse로 정의됨- TermsAgreementResponse(data class id, email, nickname, provider, termsAgreed) 필드 모두 non-nullable
⇒ Any/Unit 관련 컨버터 오류 가능성은 제거되었습니다.
다만 서버가 PUT /api/v1/users/me/terms-agreement 호출 시 200(또는 201) + JSON Body를 반환하는지 확인이 필요합니다.
– 204 No Content 응답일 경우 직렬화 실패가 재현될 수 있으므로, 빈 Body 대응(예: Unit 반환 또는 빈 JSON 지원)도 검토해주세요.위 사항 확인 후 회신 부탁드립니다.
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt (1)
242-250: LGTM — TermsAgreementResponse → TermsAgreementModel 매핑이 정확합니다.
필드 매핑(id/email/nickname/provider/termsAgreed) 누락/오타 없이 일치합니다.core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt (2)
8-10: LGTM — 계약 변경이 명확하며, 도메인에 유용한 데이터가 상위로 전달됩니다.
Result → Result 변경으로 직렬화 문제를 해소하고, 호출자가 즉시 유용한 데이터를 활용할 수 있습니다.
8-10: 호출부 검증 완료:agreeTerms변경에 따른 영향 없음
DefaultUserRepository.agreeTerms에서runSuspendCatching으로Result<TermsAgreementModel>을 반환TermsAgreementPresenter(feature/login)에서.onSuccess { … }로 반환값을 처리- 다른 곳에서
Result<Unit>타입은 AuthRepository 계열 메서드에만 사용,agreeTerms에는 불필요위 호출부 모두 반환값을 무시하거나 패턴 매칭 없이 사용한 곳이 없어 컴파일 에러·로직 누락 우려가 없습니다. 추가 조치 불필요합니다.
| internal fun TermsAgreementResponse.toModel(): TermsAgreementModel { | ||
| return TermsAgreementModel( | ||
| id = id, | ||
| email = email, | ||
| nickname = nickname, | ||
| provider = provider, | ||
| termsAgreed = termsAgreed, | ||
| ) | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
널 안정성/타입 정합성 확인 및 매퍼 단위 테스트 권장
- Response 쪽 필드가 모두 non-null String/Boolean 인지 확인 바랍니다(Nullable 이면 컴파일/런타임 이슈 발생 가능).
- 간단한 매퍼 단위 테스트를 추가해 회귀를 방지하는 것을 권장합니다.
다음 스크립트로 Response 정의의 필드 타입을 빠르게 확인하세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] TermsAgreementResponse 정의 및 필드 타입 확인"
rg -n --pretty --hidden --glob '!*build*' 'data\s+class\s+TermsAgreementResponse' -A 20 -B 2 || true
echo
echo "[2] 매퍼 호출 경로 확인(실제 사용 여부)"
rg -n --pretty --hidden --glob '!*build*' 'toModel\(\)\s*$' -A 1 -B 1 | rg -n 'TermsAgreementResponse' -A 1 -B 1 || trueLength of output: 840
TermsAgreementResponse 매퍼 null 안정성 확인 완료 및 테스트 추가 권장
- core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/TermsAgreementResponse.kt 의
id,email,nickname,provider는 모두 non-nullString,termsAgreed는 non-nullBoolean임을 확인했습니다. - 현재 코드베이스에서
toModel()호출 경로가 검색되지 않으므로, 실제 사용 여부를 검토하고 사용하지 않는다면 매퍼 제거를 고려해주세요. - TermsAgreementResponse → TermsAgreementModel 매핑 로직에 대한 단위 테스트(예: 다양한 입력 값에 대한 필드 매핑 검증)를 추가해 회귀를 방지할 것을 권장합니다.
🤖 Prompt for AI Agents
In
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt
around lines 242 to 250, verify if the toModel() extension function for
TermsAgreementResponse is actually used in the codebase; if not, consider
removing it to reduce dead code. Additionally, add unit tests for this mapping
function to ensure all fields are correctly mapped from TermsAgreementResponse
to TermsAgreementModel, covering various input scenarios to prevent regressions.
There was a problem hiding this comment.
@coderabbitai 너 혹시 문제 원인 암?
- Unable to create converter for class java.lang.Object for method ReedService.agreeTerms
- kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
Please ensure that class is marked as '@serializable' and that the serialization compiler plugin is applied. - [Android] [R8]
IllegalArgumentExceptionthrown when calling a service operation with unused return type square/retrofit#4011
TermsAgreementPresenter에서 agreeTerms 함수 호출시 위 문제 발생했었음
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# ReedService의 agreeTerms 메서드 정의 확인
echo "=== ReedService의 agreeTerms 메서드 정의 확인 ==="
fd -e kt "ReedService" --exec grep -l "agreeTerms" {} \; | head -5 | xargs cat 2>/dev/null | grep -A 3 -B 3 "agreeTerms"
echo -e "\n=== TermsAgreementResponse 클래스 정의 확인 ==="
fd -e kt "TermsAgreementResponse" --exec cat {} \; | head -50
Length of output: 1076
@easyhooon 네, 문제 원인을 알고 있습니다!
이 에러는 Retrofit과 kotlinx.serialization을 함께 사용할 때 Unit 반환 타입을 처리하지 못해서 발생한 문제입니다.
문제 원인 분석
- Unit 타입의 특성: Kotlin의
Unit은 Java의void와 유사하지만, 실제로는 객체입니다 - Serializer 부재: kotlinx.serialization은
Unit에 대한 기본 serializer를 제공하지 않습니다 - Retrofit의 처리: Retrofit이 응답을 역직렬화하려 할 때:
Unit에 대한 serializer를 찾을 수 없음Any(Java의Object)로 폴백 시도Any에 대한 serializer도 없어서 최종적으로 실패
해결 방법
PR에서 적용한 해결책이 정확합니다:
- 이전:
suspend fun agreeTerms(...): Unit - 수정:
suspend fun agreeTerms(...): TermsAgreementResponse
실제 응답 데이터 모델(TermsAgreementResponse)을 반환하도록 변경하여, kotlinx.serialization이 적절한 serializer를 찾을 수 있게 되었습니다.
대안적 해결 방법
만약 정말로 응답 본문이 필요 없다면:
Response<Void>사용ResponseBody사용 후 무시- Custom Call Adapter 구현
하지만 현재 API가 실제로 사용자 정보를 반환하므로, 현재 해결책이 가장 적절합니다!
✏️ Learnings added
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#122
File: core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt:242-250
Timestamp: 2025-08-08T17:23:23.791Z
Learning: Reed-Android 프로젝트에서 Retrofit과 kotlinx.serialization을 함께 사용할 때, API 메서드가 Unit을 반환하면 "Serializer for class 'Any' is not found" 에러가 발생한다. 이는 kotlinx.serialization이 Unit 타입에 대한 serializer를 제공하지 않기 때문이며, 실제 응답 모델을 반환하도록 변경하여 해결한다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.
seoyoon513
left a comment
There was a problem hiding this comment.
정말 미스테리했던 버그.. Service에서 Response 타입 지정햇는데 repository에서 반환 타입을 Unit으로 했다고 터져버린 😢
|
@coderabbitai 너 혹시 문제 원인 암? Unable to create converter for class java.lang.Object for method ReedService.agreeTerms TermsAgreementPresenter에서 agreeTerms 함수 호출시 위 문제 발생했었음 |
|
Tip For best results, initiate chat on the files or code changes. @easyhooon 원인 정리해드릴게요.
추가 체크 포인트
필요하시면 서비스/레포 시그니처와 safeApiCall 제네릭 추론도 같이 점검해드릴게요. 🧠 Learnings used |
|
Tip For best results, initiate chat on the files or code changes. @easyhooon 네, 문제 원인 알고 있습니다! 근본 원인:
해결 방법: 이는 Retrofit + kotlinx.serialization 조합에서 흔히 발생하는 이슈입니다. 서버가 빈 응답(204 No Content)을 보내는 게 아니라 실제 JSON 데이터를 반환하는 경우, 항상 구체적인 타입을 명시해야 합니다. 🧠 Learnings used |
|
그니까 니 말은 @PUT("api/v1/users/me/terms-agreement")
suspend fun agreeTerms(@Body termsAgreementRequest: TermsAgreementRequest): TermsAgreementResponseinternal class DefaultUserRepository @Inject constructor(
private val service: ReedService,
private val onboardingDataSource: OnboardingDataSource,
) : UserRepository {
override suspend fun agreeTerms(termsAgreed: Boolean) = runSuspendCatching {
service.agreeTerms(TermsAgreementRequest(termsAgreed))
Unit
}
//...
}retrofit interface에 Response를 제대로 작성해도 Repository 구현체에서 return을 Unit 으로 해버리면 release 환경에서 kotlinx serialization converter에게 Any 타입을 직렬화하게 만들어 직렬화 실패를 트리거할 수 있다는거야?(정확힌 Unit이긴한데 에러 문구엔 Any라고 언급이 되어있어서) |
|
Tip For best results, initiate chat on the files or code changes. @easyhooon 거의 맞는데, 약간의 차이가 있어요! 실제로는 이렇습니다:
진짜 문제는: 왜 Any로 표시되나요?
해결책: ✏️ Learnings added
🧠 Learnings used |
@coderabbitai Repository 함수의 return 타입이 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 145 🏁 Script executed: Length of output: 227 🏁 Script executed: Length of output: 18386 🏁 Script executed: Length of output: 495 🏁 Script executed: Length of output: 335 @easyhooon 아, 좋은 질문이에요! 제가 설명을 명확하게 못 드렸네요. Repository 반환 타입과 Retrofit 파싱은 직접적인 관계가 없습니다. 실제 흐름을 보면:
진짜 문제는:
즉:
Repository 반환 타입이 Retrofit 파싱에 영향을 주는 게 아니라, 타입 시스템과 제네릭 처리 과정에서 문제가 발생한 거예요! 🧠 Learnings used |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 16273 @easyhooon 아니에요, 좀 더 정확히 설명할게요! Repository의 return type ( 실제 흐름:
그럼 왜 에러가 발생했나요? 에러 메시지를 다시 보면 "Unable to create converter for class java.lang.Object for method ReedService.agreeTerms"라고 나와있죠. 진짜 문제는 아마도:
정리하면:
이래서 Debug 빌드에선 정상 동작하지만 Release 빌드에서만 문제가 발생한 거예요! 🧠 Learnings used |
🔗 관련 이슈
📙 작업 설명
🧪 테스트 내역 (선택)
💬 추가 설명 or 리뷰 포인트 (선택)
Please ensure that class is marked as '@serializable' and that the serialization compiler plugin is applied.
IllegalArgumentExceptionthrown when calling a service operation with unused return type square/retrofit#4011Summary by CodeRabbit