[Feat] 온보딩 프로필 - 닉네임 중복체크 API연동#150
Conversation
📝 WalkthroughWalkthrough닉네임 중복 검사 API/도메인 흐름과 온보딩 UI·ViewModel 상태 관리가 추가되었고, 온보딩 네비게이션이 중첩 그래프로 재구성되며 NavBackStackEntry용 sharedViewModel 확장 함수가 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Profile as OnboardingProfileScreen
participant VM as OnboardingViewModel
participant Repo as UserRepository
participant API as UserApi
User->>Profile: 닉네임 입력
Profile->>VM: updateNickname(nickname)
VM->>VM: 유효성 검사 및 uiState 갱신
User->>Profile: 확인 버튼 클릭
Profile->>VM: checkNicknameDuplication()
VM->>Repo: checkNickname(nickname)
Repo->>API: GET /api/v1/users/nickname/check?nickname=...
API-->>Repo: NicknameCheckResponseDto
Repo-->>VM: Result<NicknameCheckModel>
VM->>Profile: uiState(isNicknameAvailable) 변경
Profile->>User: 가용성 표시/다음 행동 활성화
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 2
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/flint/presentation/onboarding/OnboardingProfileScreen.kt`:
- Around line 123-144: The buttons only change visual state via the
FlintButtonState but remain clickable because FlintBasicButton uses an enabled
parameter to block clicks; update the two FlintBasicButton calls (the "확인"
button that calls onCheckNickname and the "시작하기" button that calls onNextClick)
to pass enabled = isValid (or enabled = isValid && isNicknameAvailable == true
as appropriate) for the 확인 button and enabled = canProceed for the 시작하기 button
so that the visual Disabled state matches actual clickability and prevent
onCheckNickname/onNextClick from firing when inputs are invalid.
In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`:
- Around line 36-47: The checkNicknameDuplication flow captures currentNickname
but may apply an out-of-date response; modify checkNicknameDuplication (and its
viewModelScope.launch block that calls userRepository.checkNickname and handles
onSuccess) to compare the nickname from the request with the latest state before
updating: store currentNickname, call checkNickname(currentNickname), and inside
onSuccess verify that _uiState.value.nickname == currentNickname before calling
_uiState.update to set isNicknameAvailable; if they differ, ignore the response.
🧹 Nitpick comments (1)
app/src/main/java/com/flint/presentation/onboarding/OnboardingDoneScreen.kt (1)
19-32: 공유 ViewModel 연결 여부 확인OnboardingDoneRoute에 ViewModel 파라미터가 추가됐지만 현재 화면 내부에서 사용/전달이 없습니다. 이 화면에서도 공유 상태/이벤트를 쓰려는 의도라면 전달·사용을 추가하고, 아니라면 불필요한 인스턴스 생성을 줄이는 방향을 검토해 주세요.
| fun checkNicknameDuplication() { | ||
| val currentNickname = _uiState.value.nickname | ||
|
|
||
| viewModelScope.launch { | ||
| userRepository.checkNickname(currentNickname).onSuccess { result -> | ||
| _uiState.update { currentState -> | ||
| currentState.copy( | ||
| isNicknameAvailable = result.isAvailable, | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "OnboardingViewModel.kt" -type fRepository: imflint/Flint-Android
Length of output: 141
🏁 Script executed:
git ls-files | grep -i onboarding | head -20Repository: imflint/Flint-Android
Length of output: 1034
🏁 Script executed:
cat -n app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.ktRepository: imflint/Flint-Android
Length of output: 2242
🏁 Script executed:
grep -n "checkNickname\|userRepository\|onSuccess" app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.ktRepository: imflint/Flint-Android
Length of output: 238
🏁 Script executed:
find . -name "UserRepository.kt" -type f | head -5Repository: imflint/Flint-Android
Length of output: 130
🏁 Script executed:
grep -r "interface UserRepository\|class.*UserRepository" --include="*.kt" | head -10Repository: imflint/Flint-Android
Length of output: 149
🏁 Script executed:
cat -n app/src/main/java/com/flint/domain/repository/UserRepository.ktRepository: imflint/Flint-Android
Length of output: 1092
🏁 Script executed:
grep -r "fun checkNickname" --include="*.kt" -A 10Repository: imflint/Flint-Android
Length of output: 3248
요청 중 닉네임이 바뀌면 이전 응답의 결과가 현재 상태에 잘못 적용될 수 있음
사용자가 닉네임을 입력하고 중복검사를 요청한 후, 응답이 도착하기 전에 닉네임을 다시 변경하면 이전 요청의 응답이 새 닉네임에 대해 잘못된 isNicknameAvailable 값을 설정합니다.
응답을 적용하기 전에 요청 시점의 닉네임과 현재 상태의 닉네임이 일치하는지 확인하세요:
🔧 제안 수정안
fun checkNicknameDuplication() {
- val currentNickname = _uiState.value.nickname
+ val requestedNickname = _uiState.value.nickname
viewModelScope.launch {
- userRepository.checkNickname(currentNickname).onSuccess { result ->
- _uiState.update { currentState ->
- currentState.copy(
- isNicknameAvailable = result.isAvailable,
- )
- }
- }
+ userRepository.checkNickname(requestedNickname).onSuccess { result ->
+ _uiState.update { currentState ->
+ if (currentState.nickname != requestedNickname) return@update currentState
+ currentState.copy(
+ isNicknameAvailable = result.isAvailable,
+ )
+ }
+ }
}
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/onboarding/OnboardingViewModel.kt`
around lines 36 - 47, The checkNicknameDuplication flow captures currentNickname
but may apply an out-of-date response; modify checkNicknameDuplication (and its
viewModelScope.launch block that calls userRepository.checkNickname and handles
onSuccess) to compare the nickname from the request with the latest state before
updating: store currentNickname, call checkNickname(currentNickname), and inside
onSuccess verify that _uiState.value.nickname == currentNickname before calling
_uiState.update to set isNicknameAvailable; if they differ, ignore the response.
| @GET("/api/v1/users/nickname/check") | ||
| suspend fun checkNickname( |
There was a problem hiding this comment.
p3: 현재의 함수명에 중복이라는 의미는 포함되지 않은 것 같아요. 추후 중복 외에 다른 요소도 확인하는 상황을 예상해서 구현된 부분일까요?
| modifier | ||
| .fillMaxSize() | ||
| .background(color = FlintTheme.colors.background) | ||
| .statusBarsPadding(), |
There was a problem hiding this comment.
p3: statusBarsPadding()을 제거해주신 이유가 무엇인가요? 👀
| maxLines = 1, | ||
| maxLength = OnboardingProfileUiState.MAX_LENGTH, | ||
| onValueChange = onNicknameChange, | ||
| trailingContent = { | ||
| Text( | ||
| text = "${nickname.length}/$maxLength", | ||
| text = "${nickname.length}/${OnboardingProfileUiState.MAX_LENGTH}", |
There was a problem hiding this comment.
p3: 무엇에 대한 MAX_LENGTH일까요? 이것이 드러나는 변수명이면 더욱 좋을 것 같아요. 👍
| companion object { | ||
| const val MAX_LENGTH = 8 | ||
| const val MIN_LENGTH = 2 | ||
| } | ||
|
|
||
| //다믐단게 활성화 | ||
| val canProceed: Boolean | ||
| get() = isValid && isNicknameAvailable == true |
There was a problem hiding this comment.
p3: companion object가 가장 아래에 있는 것이 Kotlin 공식 코딩 컨벤션이니 참고해주세요!
p3: canProceed가 계산된 프로퍼티(getter만 존재하는 상황)로 구현된 이유가 있을까요? 👀
| data class OnboardingProfileUiState( | ||
| val nickname: String = "", | ||
| val isValid: Boolean = false, | ||
| val isNicknameAvailable: Boolean? = null, | ||
| ) { |
There was a problem hiding this comment.
p3: 파라미터 기본값을 사용하는 이유는 무엇인가요? 문제는 없을까요?
| fun updateNickname(nickname: String) { | ||
| if (nickname.length <= OnboardingProfileUiState.MAX_LENGTH) { | ||
| _uiState.update { currentState -> | ||
| currentState.copy( | ||
| nickname = nickname, | ||
| isValid = nickname.length >= OnboardingProfileUiState.MIN_LENGTH, |
There was a problem hiding this comment.
p3: nickname의 길이를 두 번 검사하고 있어요. 한 번에 검사할 수 없을까요?
nickname.length in MIN_LENGTH .. MAX_LENGTH
| const val MAX_LENGTH = 8 | ||
| const val MIN_LENGTH = 2 |
There was a problem hiding this comment.
p3
유효성 검사 시에 Range나 정규식으로 관리할 수도 있을 것 같아요
| val isValid: Boolean = false, | ||
| val isNicknameAvailable: Boolean? = null, |
There was a problem hiding this comment.
p2
isValid와 isNicknameAvailable이 정확히 어떤 목적으로 두개가 존재하는 걸까요?
닉네임 형식 오류도 결과적으로 “사용할 수 없는 닉네임입니다” 토스트를 노출하는 것 같던데, 구분하신 이유가 있는지 궁금합니다.
형식 체크와 중복 확인을 위한 구분값이라면, 네이밍이 변경되어야할 것 같아요. 그게 아니더라도 두개로 관리할 필요가 있을까? 하는 생각이 드네용
There was a problem hiding this comment.
isValid가 유효성 검사를 위한 값이라면 결국 nickname과 연관된 상태 아닌가요? canProceed처럼 백킹 프로퍼티로 구현하신 게 아니라, 프로퍼티로 하신 이유가 있으신지 궁금합니다
| const val MIN_LENGTH = 2 | ||
| } | ||
|
|
||
| //다믐단게 활성화 |
…ng-contnet-api # Conflicts: # app/src/main/java/com/flint/data/api/UserApi.kt # app/src/main/java/com/flint/domain/repository/UserRepository.kt
📮 관련 이슈
📌 작업 내용
📸 스크린샷
Screen_recording_20260121_122119.mp4
😅 미구현