Conversation
Walkthrough온보딩 프로필 제출 기능과 관련된 데이터 계층(서비스/데이터소스/리포지토리/DTO/모델), DI 바인딩, 프레젠테이션 계층(온보딩 UI, 입력 검증, 사이드이펙트, 네비게이션), IME 패딩 확장, 및 액티비티 설정이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant UI as OnboardingInformation<br/>Screen
participant VM as OnboardingInformation<br/>ViewModel
participant Repo as OnboardingProfile<br/>Repository
participant DS as OnboardingProfile<br/>DataSource
participant Service as OnboardingProfile<br/>Service
participant API as API Server
participant TM as TokenManager
User->>UI: 이름/나이 입력
UI->>VM: onNameChanged/onAgeChanged
VM->>VM: uiState 업데이트/검증
User->>UI: Next 클릭
UI->>VM: onNextClicked()
VM->>Repo: postOnboardingProfile(requestModel)
Repo->>DS: postOnboardingProfile(requestDto)
DS->>Service: postOnboardingProfile(requestDto)
Service->>API: POST /api/onboarding/profiles
API-->>Service: BaseResponse<ResponseDto>
Service-->>DS: 응답 반환
DS-->>Repo: 응답 반환
Repo->>Repo: 응답 toModel()
Repo-->>VM: Result<ResponseModel>
VM->>TM: saveId(id)
TM-->>VM: 완료
VM->>VM: _sideEffect.emit(NavigateToHome)
VM-->>UI: sideEffect 수집
UI->>UI: navigateToHome()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.kt`:
- Around line 101-106: 현재 onGloballyPositioned 블록에서 consumePadding이 루트 높이에서
(positionInWindow().y + size.height)를 뺀 값으로 계산되는데, coerceAtLeast(0)가 뺄셈 전 항목에만
적용되어 최종 결과가 음수가 될 수 있습니다; 수정 방법은 onGloballyPositioned 내부의 계산 전체에 대해 최종값을 0 이상으로
클램프하도록 변경하는 것으로, 예컨대 computeValue = findRootCoordinates().size.height -
(coordinates.positionInWindow().y + coordinates.size.height).toInt();
consumePadding = computeValue.coerceAtLeast(0); 이렇게 onGloballyPositioned,
consumePadding, coordinates.findRootCoordinates(),
coordinates.positionInWindow(), coordinates.size.height식을 참조해 최종 값을
.coerceAtLeast(0)로 감싸 주세요.
In `@app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt`:
- Around line 22-26: provideOnboardingProfileService is annotated with
javax.annotation.Signed which is not a Dagger Qualifier and will not
differentiate bindings, causing injection failures at
OnboardingProfileDataSourceImpl; either replace `@Signed` with `@Singleton` to match
CalendarService's scope, or define a custom Dagger `@Qualifier` (apply it to both
the provider provideOnboardingProfileService and the injection site in
OnboardingProfileDataSourceImpl) so the signed Retrofit binding is uniquely
identified and resolves correctly.
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/OnboardingProfileRepositoryImpl.kt`:
- Around line 18-22: Replace the forced unwrap of response.data in the
suspendRunCatching block: call
onboardingProfileDataSource.postOnboardingProfile(request = request.toDto()),
capture the response, and explicitly handle a null data (response.data) before
calling toModel(); e.g., if data is null return/emit a Result.failure or throw a
clear exception with context rather than using !!, or alternatively make the
DTO's data field non-nullable if it is guaranteed by the API; reference
onboardingProfileDataSource.postOnboardingProfile, suspendRunCatching,
response.data, toModel() and toDto() when applying the change.
In `@app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt`:
- Around line 26-35: The navOptions in clearStackNavOptions use popUpTo(0),
which references a non-existent numeric ID for typed (`@Serializable`) routes;
replace that with a valid target such as the navigation graph start destination
or a route string. Update clearStackNavOptions to call
popUpTo(navController.graph.startDestinationId) (or popUpTo("YourStartRoute")
when using route strings) so popUpTo targets an actual destination; keep
launchSingleTop and restoreState as-is. Ensure the change is made where
clearStackNavOptions (and any other popUpTo usages) is defined so the typed
navigation IDs are valid.
In
`@app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationViewModel.kt`:
- Around line 44-54: The onNextClicked() flow in InformationViewModel should
defensively handle empty or non-numeric ages to avoid NumberFormatException:
instead of calling uiState.value.age.toInt() unguarded, parse with
uiState.value.age.toIntOrNull() (or validate that uiState.value.age.isNotBlank()
&& matches(\\d+)) and if null/invalid emit an appropriate side effect/error or
return early; then continue to call
onboardingProfileRepository.postOnboardingProfile(...) with
OnboardingProfileRequestModel(name = uiState.value.username, age = safeInt) and
preserve the existing success path that calls tokenManager.saveId(response.id)
and _sideEffect.emit(InformationSideEffect.NavigateToHome).
| suspendRunCatching { | ||
| onboardingProfileDataSource.postOnboardingProfile( | ||
| request = request.toDto() | ||
| ).data!!.toModel() | ||
| } |
There was a problem hiding this comment.
data!! 강제 언래핑은 NPE 위험이 있습니다.
API 응답이 성공적으로 반환되더라도 data가 null인 경우 NullPointerException이 발생합니다. suspendRunCatching이 예외를 Result.failure로 변환하긴 하지만, 명시적인 null 처리가 더 안전합니다.
🛠️ 제안하는 수정 방안
override suspend fun postOnboardingProfile(
request: OnboardingProfileRequestModel
): Result<OnboardingProfileResponseModel> =
suspendRunCatching {
onboardingProfileDataSource.postOnboardingProfile(
request = request.toDto()
- ).data!!.toModel()
+ ).data?.toModel()
+ ?: throw IllegalStateException("Onboarding profile response data is null")
}또는 API 응답 구조에서 data가 항상 non-null임이 보장된다면, DTO의 data 필드를 non-nullable로 변경하는 것이 더 근본적인 해결책입니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| suspendRunCatching { | |
| onboardingProfileDataSource.postOnboardingProfile( | |
| request = request.toDto() | |
| ).data!!.toModel() | |
| } | |
| suspendRunCatching { | |
| onboardingProfileDataSource.postOnboardingProfile( | |
| request = request.toDto() | |
| ).data?.toModel() | |
| ?: throw IllegalStateException("Onboarding profile response data is null") | |
| } |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/OnboardingProfileRepositoryImpl.kt`
around lines 18 - 22, Replace the forced unwrap of response.data in the
suspendRunCatching block: call
onboardingProfileDataSource.postOnboardingProfile(request = request.toDto()),
capture the response, and explicitly handle a null data (response.data) before
calling toModel(); e.g., if data is null return/emit a Result.failure or throw a
clear exception with context rather than using !!, or alternatively make the
DTO's data field non-nullable if it is guaranteed by the API; reference
onboardingProfileDataSource.postOnboardingProfile, suspendRunCatching,
response.data, toModel() and toDto() when applying the change.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt`:
- Around line 229-233: The Text composable rendering errorText (in
InformationScreen's error block) uses Color.Transparent when errorCase is false
which hides visually but remains in accessibility tree; update the Text to
conditionally add semantics { invisibleToUser() } (using modifier.semantics)
when errorCase is false so TalkBack won't read it, and add the suggested imports
(androidx.compose.ui.semantics.invisibleToUser and
androidx.compose.ui.semantics.semantics); locate the Text that references
errorText and errorCase and apply the conditional modifier there.
| Text( | ||
| text = errorText, | ||
| style = CherrishTheme.typography.body1R14, | ||
| color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt | sed -n '220,240p'Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 800
🏁 Script executed:
# Also check imports at the beginning of the file
head -50 app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.ktRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 2424
🏁 Script executed:
# Check if semantics imports exist anywhere
rg "import.*semantics" app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.ktRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 56
🏁 Script executed:
# Search for similar conditional visibility patterns with semantics
rg "invisibleToUser|semantics.*invisibleToUser" --type ktRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 100
🏁 Script executed:
# Check for other error text handling patterns
rg "Color\.Transparent" --type kt -B 2 -A 2 | head -40Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 100
🏁 Script executed:
# Search for similar conditional visibility patterns with semantics (corrected)
rg "invisibleToUser|semantics" --type kotlin | head -20Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 56
🏁 Script executed:
# Check for other Color.Transparent usage
rg "Color\.Transparent" --type kotlin -B 2 -A 2Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 6059
🏁 Script executed:
# Check how errorText is defined and set
rg "errorText" app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt -B 3 -A 1Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 727
🏁 Script executed:
# Check the full composable function signature to understand the context
sed -n '180,235p' app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.ktRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 1877
🏁 Script executed:
# Check build.gradle for Compose version
find . -name "build.gradle*" -type f | xargs grep -l "compose" | head -3Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 115
🏁 Script executed:
# Check if there's gradle or version information
cat build.gradle 2>/dev/null || cat build.gradle.kts 2>/dev/null | head -50Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 424
🏁 Script executed:
# Check app's build.gradle.kts for Compose dependencies
cat app/build.gradle.kts | grep -A 30 "dependencies"Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 1045
🏁 Script executed:
# Check gradle/libs.versions.toml for version definitions
cat gradle/libs.versions.toml | head -80Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 3033
🏁 Script executed:
# Check if semantics is used elsewhere in the codebase
rg "semantics\s*\{" --type kotlin | head -10Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 56
🏁 Script executed:
# Check Compose Material3 and Compose UI documentation for invisibleToUser
# Let's also verify the exact import path
rg "androidx.compose.ui.semantics" app/ --type kotlinRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 56
🏁 Script executed:
# Check for other error message patterns in the codebase
rg "errorCase|error.*Text" app/src/main/java/com/cherrish/android/presentation --type kotlin -A 2 | head -30Repository: TEAM-Cherrish/Cherrish-Android
Length of output: 2509
🏁 Script executed:
# Check the specific composable context more thoroughly
sed -n '175,245p' app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.ktRepository: TEAM-Cherrish/Cherrish-Android
Length of output: 2284
에러 상태가 아닐 때 보이지 않는 에러 텍스트가 접근성 트리에 남아 TalkBack에서 읽힙니다.
Color.Transparent는 시각적으로만 숨기므로, 에러가 없을 때는 invisibleToUser()로 접근성에서도 제외해야 합니다.
🔧 제안 수정
Text(
text = errorText,
style = CherrishTheme.typography.body1R14,
- color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent
+ color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent,
+ modifier = if (errorCase) Modifier else Modifier.semantics { invisibleToUser() }
)필요한 import:
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt`
around lines 229 - 233, The Text composable rendering errorText (in
InformationScreen's error block) uses Color.Transparent when errorCase is false
which hides visually but remains in accessibility tree; update the Text to
conditionally add semantics { invisibleToUser() } (using modifier.semantics)
when errorCase is false so TalkBack won't read it, and add the suggested imports
(androidx.compose.ui.semantics.invisibleToUser and
androidx.compose.ui.semantics.semantics); locate the Text that references
errorText and errorCase and apply the conditional modifier there.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt`:
- Around line 57-60: The empty onLogFailure block in
OnboardingInformationViewModel leaves API failures unhandled; update the
onLogFailure handler to emit a user-facing error side effect (use
_sideEffect.emit) with the failure message or a localized string and provide a
retry option (e.g., emit InformationSideEffect.ShowError or a new
ShowErrorWithRetry side effect), so failures after the tokenManager.saveId /
InformationSideEffect.NavigateToHome flow surface to the UI and allow retry.
♻️ Duplicate comments (2)
app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt (1)
28-31:popUpTo(0)대상이 유효한지 확인 필요
타입드 라우팅을 쓰는 경우0은 유효한 목적지 ID가 아닐 수 있습니다. 그래프의 실제 ID(예:appState.navController.graph.id)나 시작 목적지를 사용해주세요.🛠️ 수정 제안
val clearStackNavOptions = navOptions { - popUpTo(0) { inclusive = true } + popUpTo(appState.navController.graph.id) { inclusive = true } launchSingleTop = true restoreState = false }app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt (1)
229-233: 에러 텍스트 접근성 문제
Color.Transparent는 시각적으로만 텍스트를 숨기며, TalkBack 등 스크린 리더에서는 여전히 읽힙니다. 에러 상태가 아닐 때는semantics { invisibleToUser() }를 적용하여 접근성 트리에서도 제외해야 합니다.🔧 수정 제안
+import androidx.compose.ui.semantics.invisibleToUser +import androidx.compose.ui.semantics.semanticsText( text = errorText, style = CherrishTheme.typography.body1R14, - color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent + color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent, + modifier = if (errorCase) Modifier else Modifier.semantics { invisibleToUser() } )
🧹 Nitpick comments (5)
app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationUiState.kt (1)
10-15: age 파싱 중복 제거로 조건식 단순화
age.toIntOrNull()가 두 번 호출되어 중복 파싱이 발생합니다.ageInt를 재사용해 조건을 단순화해주세요.♻️ 수정 제안
val buttonEnabled: Boolean get() { - val ageInt = age.toIntOrNull() ?: return false - return username.isNotBlank() && age.toIntOrNull()?.let { it > 0 } == true && - ageInt in 1..100 && username.length <= 7 + val ageInt = age.toIntOrNull() ?: return false + return username.isNotBlank() && + username.length <= 7 && + ageInt in 1..100 }app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingScreen.kt (1)
99-114: paddingValues를 루트 컨테이너에 먼저 적용하세요
현재 Column에만paddingValues가 적용돼 하단 오버레이 영역은 스캐폴드 인셋을 공유하지 않습니다. Box 루트에paddingValues를 적용하면 인셋 처리가 일관됩니다. Based on learnings, 루트에 먼저paddingValues를 적용하는 패턴을 유지해주세요.♻️ 수정 제안
Box( modifier = modifier .fillMaxSize() .background( brush = Brush.verticalGradient( colors = persistentListOf(colors.graStart, colors.graEnd), startY = 0f, endY = gradientEndY ) - ) + ) + .padding(paddingValues) ) { Column( modifier = Modifier - .fillMaxSize() - .padding(paddingValues) + .fillMaxSize() ) {Also applies to: 154-162
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
84-94: NavOptions 매개변수의 일관성 검토 필요새로 추가된 navigation helper 메서드들은
NavOptions를 필수 매개변수로 받고 있지만,OnboardingInformationNavigator.kt의 extension 함수(navigateToOnboardingInformation)는NavOptions? = null로 nullable 기본값을 사용합니다. 일관성을 위해 nullable로 변경하거나, 호출부에서 항상 navOptions를 제공해야 하는 이유를 명확히 하는 것이 좋습니다.♻️ 일관성을 위한 수정 제안
- fun navigateToOnboarding(navOptions: NavOptions) { + fun navigateToOnboarding(navOptions: NavOptions? = null) { navController.navigateToOnboarding(navOptions) } - fun navigateToOnboardingInformation(navOptions: NavOptions) { + fun navigateToOnboardingInformation(navOptions: NavOptions? = null) { navController.navigateToOnboardingInformation(navOptions) } - fun navigateToHome(navOptions: NavOptions) { + fun navigateToHome(navOptions: NavOptions? = null) { navController.navigateToHome(navOptions) }app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt (1)
66-67: 에러 상태 계산의 불필요한 recomposition 비용
viewModel.onNameErrorCase(uiState.username)와viewModel.onAgeErrorCase(uiState.age)가 매 recomposition마다 호출됩니다. 이 로직은 ViewModel의InformationUiState에nameErrorCase와ageErrorCase필드를 추가하여 상태 변경 시에만 계산되도록 최적화할 수 있습니다.♻️ ViewModel에서 파생 상태로 관리하는 방안
InformationUiState에 에러 상태를 포함시키고,onNameChanged/onAgeChanged에서 함께 업데이트:// InformationUiState.kt data class InformationUiState( val username: String = "", val age: String = "", val nameErrorCase: Boolean = false, val ageErrorCase: Boolean = false ) { val buttonEnabled: Boolean get() = username.isNotBlank() && age.isNotBlank() && !nameErrorCase && !ageErrorCase }// OnboardingInformationViewModel.kt fun onNameChanged(name: String) { _uiState.update { it.copy( username = name, nameErrorCase = name.length > 7 ) } }app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt (1)
46-46: 사소한 스타일 개선
?.let앞의 불필요한 공백을 제거하면 더 깔끔합니다.- fun onAgeErrorCase(age: String): Boolean = age.toIntOrNull() ?.let { it > 100 } ?: false + fun onAgeErrorCase(age: String): Boolean = age.toIntOrNull()?.let { it > 100 } ?: false
| ).onSuccess { response -> | ||
| tokenManager.saveId(response.id) | ||
| _sideEffect.emit(InformationSideEffect.NavigateToHome) | ||
| }.onLogFailure {} |
There was a problem hiding this comment.
API 실패 시 사용자 피드백 부재
onLogFailure {}가 빈 블록으로 되어 있어 API 호출 실패 시 사용자에게 아무런 피드백이 제공되지 않습니다. 오류 메시지를 표시하거나 재시도 옵션을 제공하는 것이 좋습니다.
🔧 에러 처리 개선 제안
// InformationSideEffect에 추가
sealed interface InformationSideEffect {
data object NavigateToHome : InformationSideEffect
+ data class ShowError(val message: String) : InformationSideEffect
} ).onSuccess { response ->
tokenManager.saveId(response.id)
_sideEffect.emit(InformationSideEffect.NavigateToHome)
- }.onLogFailure {}
+ }.onLogFailure {
+ _sideEffect.emit(InformationSideEffect.ShowError("프로필 저장에 실패했습니다. 다시 시도해주세요."))
+ }🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt`
around lines 57 - 60, The empty onLogFailure block in
OnboardingInformationViewModel leaves API failures unhandled; update the
onLogFailure handler to emit a user-facing error side effect (use
_sideEffect.emit) with the failure message or a localized string and provide a
retry option (e.g., emit InformationSideEffect.ShowError or a new
ShowErrorWithRetry side effect), so failures after the tokenManager.saveId /
InformationSideEffect.NavigateToHome flow surface to the UI and allow retry.
| navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT) | ||
| ) | ||
|
|
||
| requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |




Related issue 🛠
Work Description ✏️
Screenshot 📸
Screen_recording_20260118_235944.mp4
Uncompleted Tasks 😅
To Reviewers 📢
다들 화이팅팅팅
스플래서, 온보딩 머지 기다려닸가 그 부분 네비 연결까지 이 브랜치에서 해놓겠습니다~!!
(분리를 원하시면 코멘트 남겨주세요오~!!)
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선
문서/기타
✏️ Tip: You can customize this high-level summary in your review settings.