Conversation
Walkthrough회원가입·검색·프로필 수정 등 User 도메인 API 연동과 관련된 모델/서비스/레포지토리/뷰모델/컴포저블이 추가·수정되었고, DataStore 기반 TokenManager와 MyPage 고객센터 화면 및 네비게이션이 도입되었습니다. (≤50단어) Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Signup UI
participant VM as SignupViewModel
participant Repo as UserRepository
participant API as UserService
participant Store as TokenManager
UI->>VM: onNicknameChange / checkNickname()
VM->>Repo: checkNickname(nickname)
Repo->>API: GET /users/nickname/check
API-->>Repo: BaseResponse<...>
Repo-->>VM: Result
UI->>VM: fetchAliasChoices()
VM->>Repo: getAliasChoices()
Repo->>API: GET /users/aliases
API-->>Repo: BaseResponse<...>
Repo-->>VM: Result
UI->>VM: signup()
VM->>Store: getTempToken()
Store-->>VM: token or null
alt token exists
Repo->>API: POST /users/signup (Authorization)
API-->>Repo: BaseResponse<SignupResponse>
Repo-->>VM: Success
else
VM-->>UI: error (temp token missing)
end
sequenceDiagram
participant UI as Search UI
participant VM as SearchPeopleViewModel
participant Repo as UserRepository
participant API as UserService
UI->>VM: onSearchTextChanged(text)
VM->>VM: debounce 500ms
VM->>Repo: searchUsers(keyword, isFinalized=false)
Repo->>API: GET /users?keyword&isFinalized=false
API-->>Repo: BaseResponse<UserSearchResponse>
Repo-->>VM: Result -> update uiState.searchResults
UI->>VM: onFinalSearch(query)
VM->>Repo: searchUsers(keyword, isFinalized=true)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
✨ 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/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 15
🔭 Outside diff range comments (1)
app/build.gradle.kts (1)
79-79: Compose Foundation 중복/버전 충돌 위험: BOM 사용 중에 고정 버전(1.5.0) 명시 제거 권장.이미 BOM과 라이브러리 alias(libs.androidx.foundation)를 사용 중인데, 같은 모듈을 직접 버전으로 한 번 더 추가하면 버전 미스매치/해결 불일치가 생길 수 있습니다. 아래 라인 제거를 권장합니다.
- implementation("androidx.compose.foundation:foundation:1.5.0")
🧹 Nitpick comments (38)
app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt (4)
8-8: FollowerList 네이밍이 의미와 맞지 않음 — 단수 엔티티명으로 정리 제안FollowerList는 “목록”처럼 들리지만 실제로는 단일 팔로워 아이템입니다. 도메인 용어 혼동을 줄이기 위해 Follower 또는 FollowerItem 같은 단수형 네이밍을 권장합니다.
data class OthersFollowersResponse( - @SerializedName("followers") val followers: List<FollowerList>, + @SerializedName("followers") val followers: List<Follower>, ... ) -@Serializable -data class FollowerList( +@Serializable +data class Follower( ... )주의: 파일/타입명을 변경하면 사용처 전반(리포지토리, UI 모델 매핑 등)도 함께 수정이 필요합니다.
Also applies to: 15-22
10-11: 중복된 페이징 신호(nextCursor, isLast) — 일관성 규칙 합의 권장서버가 nextCursor와 isLast를 함께 내려주면, 두 값이 불일치할 경우 어떤 값을 신뢰할지 규칙이 필요합니다. 예: nextCursor가 null이어도 isLast=false로 내려오는 등의 엣지 케이스.
권장:
- 저장소(Repository) 레이어에서 우선순위를 명시(isLast를 신뢰하고 nextCursor는 보조로 사용 등).
- 혹은 한 값만 사용하도록 내부 규약 문서화.
테스트로 불일치 케이스를 최소 1건 커버하면 회귀 방지에 도움이 됩니다.
6-12: 간단 KDoc 추가 제안응답 모델은 사용처가 넓어 도메인 의미(예: totalFollowerCount가 전체 팔로워 수인지, 현재 페이지 기준인지)를 짧게 KDoc으로 남겨두면 유지보수에 유리합니다.
1-22: kotlinx.serialization 어노테이션 통일 및 불필요한 Gson 의존성 제거
네트워크 모듈에서 이미retrofit2-kotlinx-serialization-converter(json.asConverterFactory(…))를 사용하고 있어@SerializedName(Gson)은 런타임에 적용되지 않습니다. 모델 클래스에서com.google.gson.annotations.SerializedName을 제거하고kotlinx.serialization.SerialName으로 통일하거나, 필드명 기반 매핑(어노테이션 없이)으로 유지해주세요. 또한,gradle/libs.versions.toml의converter-gson의존성도 함께 정리하는 것을 권장합니다.
검토 대상
• app/src/main/java/com/texthip/thip/data/model/… 중@SerializedName사용 파일
• gradle/libs.versions.toml:converter-gson항목
• 참고: app/src/main/java/com/texthip/thip/data/di/NetworkModule.kt (.addConverterFactory(json.asConverterFactory(...))사용 중)예시 (OthersFollowersResponse.kt)
-package com.texthip.thip.data.model.users.response - -import com.google.gson.annotations.SerializedName -import kotlinx.serialization.Serializable +package com.texthip.thip.data.model.users.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class OthersFollowersResponse( - @SerializedName("followers") val followers: List<FollowerList>, + @SerialName("followers") val followers: List<FollowerList>, @SerialName("totalFollowerCount") val totalFollowerCount: Int, @SerialName("nextCursor") val nextCursor: String?, @SerialName("isLast") val isLast: Boolean )app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt (1)
5-9: nullable 필드 전송 방식 확인 필요(null 전송 vs. 미포함).nickname이 null일 때 서버가 "null 값을 명시적으로 전송"하는지, "필드를 생략"하기를 기대하는지 확인해주세요. kotlinx.serialization의 Json 설정(explicitNulls)에 따라 동작이 달라집니다. 서버가 미포함을 기대한다면 Retrofit의 Kotlinx Json 인스턴스를 explicitNulls = false로 설정해야 합니다.
예시(네트워크 모듈에서):
val json = Json { ignoreUnknownKeys = true explicitNulls = false // null 필드는 직렬화하지 않음 }app/src/main/res/values/strings.xml (1)
137-143: 오류 메시지 추가 좋습니다. 소소한 문장부호/일관성 제안.
- 일부 토스트/알림 문구는 종결 부호가 있고(예: 프로필 성공), 일부는 없습니다. 동일 맥락의 메시지라면 종결 부호 유무를 통일하면 좋습니다.
예시(원칙 정한 뒤 일괄 적용):
- 토스트/스낵바: 마침표 생략
- 다이얼로그 본문/긴 문장: 마침표 사용
필요 시 일괄 변경 PR 생성 도와드릴게요.app/build.gradle.kts (2)
86-90: Retrofit 컨버터 2종(Gson + Kotlinx) 동시 사용 주의 — 한 가지로 표준화 또는 순서 명시.모델에서 Gson 주석과 Kotlinx @serializable를 혼합 사용 중입니다. 실제 파싱은 Retrofit 빌더에서 등록 순서에 좌우됩니다. 팀 표준(예: Kotlinx 우선)을 정하고, 불필요한 컨버터/주석을 제거하는 것을 권장합니다.
Retrofit 구성 예:
Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // kotlinx 우선 // .addConverterFactory(GsonConverterFactory.create()) // 필요 없다면 제거 .build()
12-14: BASE_URL 미설정 시 빌드 타임에 실패하도록 방어 로직 추가 제안.현재 local.properties에 BASE_URL이 없으면 문자열 "null"이 주입될 가능성이 있습니다. 빌드 시점에 명확히 실패시키는 편이 안전합니다.
다음과 같이 개선 가능합니다:
val properties = Properties().apply { - load(project.rootProject.file("local.properties").inputStream()) + val file = project.rootProject.file("local.properties") + if (file.exists()) load(file.inputStream()) } ... - buildConfigField("String", "BASE_URL", "\"${properties["BASE_URL"]}\"") + val baseUrl = properties["BASE_URL"] as? String + ?: throw GradleException("local.properties에 BASE_URL이 정의되어 있지 않습니다.") + buildConfigField("String", "BASE_URL", "\"$baseUrl\"")Also applies to: 29-29
app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt (1)
29-31: 고객센터 네비게이션 추가 LGTM — 중복 쌓임 방지 옵션 고려동일 화면으로의 중복 네비게이션을 방지하고 상태 복원을 활용하려면 NavOptions를 함께 지정하는 것을 권장합니다.
아래와 같이 옵션을 추가해 보세요:
fun NavHostController.navigateToCustomerService() { - navigate(MyPageRoutes.CustomerService) + navigate(MyPageRoutes.CustomerService) { + launchSingleTop = true + restoreState = true + } }app/src/main/java/com/texthip/thip/MainActivity.kt (1)
21-23: 필드 주입이 현재 미사용 — 불필요하면 제거하거나 경고 억제
tokenManager가 현재 사용되지 않아 Lint 경고 및 추후 유지보수 시 혼동의 여지가 있습니다. 당장 필요 없다면 제거하거나, 임시로 유지해야 한다면 경고 억제를 권장합니다.옵션 A) 제거:
- @Inject - lateinit var tokenManager: TokenManager옵션 B) 경고 억제:
@Inject + @Suppress("unused") lateinit var tokenManager: TokenManagerapp/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt (1)
35-37: onNavigateBack에 기본값 제공으로 호출 간소화 제안다른 호출부(프리뷰 포함)에서 보일러플레이트를 줄이려면 기본값을 두는 것이 편합니다.
fun NotificationScreen( - onNavigateBack: () -> Unit + onNavigateBack: () -> Unit = {} )app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt (1)
26-26: 잔존recentWriters식별자 없음 확인 —@SerialName어노테이션 병행 지정 권장코드베이스 전반에서 기존
recentWriters참조는 더 이상 발견되지 않았습니다. 아래와 같이 Gson의@SerializedName과 Kotlin Serialization의@SerialName을 동시에 지정하면 직렬화 어노테이션 정합성을 높일 수 있습니다.
대상:
app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt (26행)
적용 예시(diff):
- @SerializedName("myFollowingUsers") val myFollowingUsers: List
+ @SerialName("myFollowingUsers")
+ @SerializedName("myFollowingUsers") val myFollowingUsers: List파일 상단에 import 추가:
import kotlinx.serialization.SerialName선택적 리네임 제안:
현재RecentWriterList타입명이 실제 의미와 다소 불일치할 수 있으므로,FollowingUserSummary등으로 이름을 검토해 보시기 바랍니다.app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (6)
60-62: 초기 페치 트리거는 적절합니다만, 네이밍/중복 호출 방지 개선 제안
LaunchedEffect(Unit)으로 최초 컴포지션 시점에 페치하는 접근은 적절합니다.- 다만 UI 상태 필드가
myFollowingUsers인데 메서드명이fetchRecentWriters()로 남아 있어 의미가 혼재됩니다. 도메인 용어에 맞춰 메서드명을fetchMyFollowings()등으로 정리하면 가독성과 유지보수성이 높아집니다.- 또한 화면 재진입/재구성 시 불필요한 재호출을 막으려면, 이미 데이터가 로드된 경우는 스킵하는 간단한 가드도 고려할 수 있습니다.
70-119: 사용되지 않는 더미 데이터 블록 제거
val mySubscriptions = listOf(...)는 이 파일 내 어디에서도 사용되지 않습니다. 유지하면 혼동을 유발하고 리소스를 낭비합니다. 제거를 권장합니다.아래처럼 전체 블록을 제거하세요:
- val mySubscriptions = listOf( - MySubscriptionData( - profileImageUrl = "https://example.com/image1.jpg", - nickname = "abcabcabcabc", - role = "문학가", - roleColor = colors.SocialScience - ), - ... - )
120-120: 중복된 상태 수집 제거
feedUiState를 이미collectAsState()하고 있는데, 동일한uiState를 다시subscriptionUiState로 수집하고 사용하지 않습니다. 불필요하므로 제거하세요.- val subscriptionUiState by viewModel.uiState.collectAsState()
220-229: 구독 바 매핑: 중복 제거를 위해 확장 함수 활용 제안PR 내에
UserItem.toMySubscriptionData()확장 함수가 추가된 것으로 보입니다. 여기서 수동 매핑 대신 확장 함수를 사용하면 중복 로직이 사라지고 필드 매핑(예: role/roleColor 등)도 통일됩니다.- val subscriptionsForBar = feedUiState.myFollowingUsers.map { user -> - MySubscriptionData( - profileImageUrl = user.profileImageUrl, - nickname = user.nickname, - role = "", - roleColor = colors.White, - subscriberCount = 0, - isSubscribed = true - ) - } + val subscriptionsForBar = feedUiState.myFollowingUsers.map { it.toMySubscriptionData() }참고: 필요 시
import com.texthip.thip.ui.feed.mock.toMySubscriptionData를 추가하세요.
208-213: Divider 표시 조건의 기준 리스트 정합성
itemsIndexed의 데이터 소스는feedStateList인데, Divider 조건은feeds.lastIndex를 기준으로 비교하고 있습니다. 현재는 길이가 동일하더라도, 이후feedStateList가 동적으로 변할 가능성을 고려하면 기준을 동일하게 맞추는 편이 안전합니다.- if (index != feeds.lastIndex) { + if (index != feedStateList.lastIndex) {Also applies to: 255-260
237-237: 불필요한 로컬 변수 제거
val profileImage = ...는 선언만 하고 사용되지 않습니다. 제거하세요.- val profileImage = feed.userProfileImage?.let { painterResource(it) }app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt (1)
1-1: ‘mock’ 패키지 위치 재고려 제안생산 코드에서 실제로 사용하는 매퍼(확장 함수)가 ui.feed.mock 패키지에 위치해 있어 혼동 소지가 있습니다. 재사용성과 의도를 위해 ui.feed.model 또는 ui.common.mapper 같은 위치로의 이동을 고려해 주세요.
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt (3)
37-41: 내부 Column은 weight로 남은 영역을 채우는 것이 Compose 권장 패턴Column 내부에서 또 다른 Column에 fillMaxSize()를 주면 부모 Column의 측정 제약과 맞지 않아 의도대로 동작하지 않을 수 있습니다. 남은 영역을 차지하며 중앙 정렬하려면 weight(1f) + fillMaxWidth() 조합을 권장합니다.
아래처럼 수정해 주세요:
- Column( - modifier = Modifier - .fillMaxSize(), + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) {
3-8: Spacer에 padding만 주는 대신 고정 높이 사용Spacer는 크기 지정 없이 padding만 주면 의도한 간격이 보장되지 않습니다. height(8.dp)로 변경하세요. 함께 height import 추가가 필요합니다.
+import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding- Spacer(modifier = Modifier.padding(top = 8.dp)) + Spacer(modifier = Modifier.height(8.dp))Also applies to: 47-47
42-46: 고객센터 이메일을 클릭 시 메일 앱으로 연결 (선택사항)접근성과 편의성을 위해 이메일을 눌렀을 때 메일 앱으로 이동하도록 처리하는 것을 권장합니다.
아래 변경이 필요합니다:
- import 및 uriHandler 추가
- 이메일 Text에 clickable 부여
+import androidx.compose.foundation.clickable +import androidx.compose.ui.platform.LocalUriHandler함수 내부(라인 25 직후 등 적절한 위치)에 추가:
val uriHandler = LocalUriHandler.current이메일 Text 수정:
- Text( - text = stringResource(R.string.customer_center_email), - style = typography.smalltitle_sb600_s18_h24, - color = colors.White - ) + Text( + text = stringResource(R.string.customer_center_email), + style = typography.smalltitle_sb600_s18_h24, + color = colors.White, + modifier = Modifier.clickable { + uriHandler.openUri("mailto:${stringResource(R.string.customer_center_email)}") + } + )app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (1)
31-31: 새 요청 시작 시 이전 에러 메시지 초기화 제안이전 실패 메시지가 다음 로딩 상태에도 남아 UI에 잔존할 수 있습니다. 로딩 시작 시 errorMessage를 null로 초기화하는 것이 안전합니다.
- _uiState.update { it.copy(isLoading = true) } + _uiState.update { it.copy(isLoading = true, errorMessage = null) }app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt (1)
15-22: 하드코딩된 경로 문자열 상수화로 오타·유지보수 리스크 감소route와 destination 문자열(“signup_flow”, “signup_nickname”, “signup_genre”)을 상수로 묶어두면 안전합니다.
아래 상수를 추가하고 사용처를 교체해 주세요.
상수 정의(파일 상단 import 아래에 추가):
private object SignupRoutes { const val Root = "signup_flow" const val Nickname = "signup_nickname" const val Genre = "signup_genre" }사용처 변경:
- navigation( - startDestination = "signup_nickname", - route = "signup_flow" - ) { - composable("signup_nickname") { navBackStackEntry -> + navigation( + startDestination = SignupRoutes.Nickname, + route = SignupRoutes.Root + ) { + composable(SignupRoutes.Nickname) { navBackStackEntry -> val parentEntry = remember(navBackStackEntry) { - navController.getBackStackEntry("signup_flow") + navController.getBackStackEntry(SignupRoutes.Root) } val viewModel: SignupViewModel = hiltViewModel(parentEntry) SignupNicknameScreen( viewModel = viewModel, onNavigateToGenre = { - navController.navigate("signup_genre") + navController.navigate(SignupRoutes.Genre) } ) } - composable("signup_genre"){ navBackStackEntry -> + composable(SignupRoutes.Genre){ navBackStackEntry -> val parentEntry = remember(navBackStackEntry) { - navController.getBackStackEntry("signup_flow") + navController.getBackStackEntry(SignupRoutes.Root) } val viewModel: SignupViewModel = hiltViewModel(parentEntry) SignupGenreScreen( viewModel = viewModel, onSignupSuccess = { navController.navigate(MainTabRoutes.Feed) { - popUpTo("signup_flow") { inclusive = true } + popUpTo(SignupRoutes.Root) { inclusive = true } } } ) }Also applies to: 19-23, 28-29, 33-37, 42-44
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (2)
51-53: 초기 데이터 로드 LaunchedEffect 도입 — 적절첫 진입 시 마이페이지 정보를 로드하도록 보장되어 UX 측면에서 좋습니다. 재진입 시 갱신 정책이 필요하다면 ViewModel init에서 트리거하거나, 화면 가시성 기준으로 repeatOnLifecycle을 고려해볼 수 있습니다.
17-18: Lifecycle-aware 수집으로 변경 제안 (collectAsStateWithLifecycle)다른 화면(Signup*)에서는 collectAsStateWithLifecycle을 사용하고 있어, 이 화면도 동일하게 맞추면 리소스 관리와 일관성에 도움이 됩니다.
아래처럼 변경을 제안드립니다:
- import androidx.compose.runtime.collectAsState + import androidx.lifecycle.compose.collectAsStateWithLifecycle- val uiState by viewModel.uiState.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle()Also applies to: 50-50
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)
45-49: 에러 Toast는 상태 대신 이벤트 스트림 사용 권장동일 문자열을 다시 발행해도 Composable 측에서 토스트가 다시 뜨지 않을 수 있고(동일 key), 재입장 시 중복 재생 리스크가 있습니다. SharedFlow/Channel 기반 단발 이벤트로 전환을 권장합니다.
원하시면 ViewModel/Composable 이벤트 전환 패치를 준비해드릴게요. 예시:
ViewModel 예시
class SignupViewModel : ViewModel() { private val _errorEvents = MutableSharedFlow<String>(extraBufferCapacity = 1) val errorEvents: SharedFlow<String> = _errorEvents fun emitError(message: String) { _errorEvents.tryEmit(message) } }Composable 예시
LaunchedEffect(Unit) { viewModel.errorEvents.collect { message -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } }app/src/main/java/com/texthip/thip/data/service/UserService.kt (1)
64-69: Authorization 헤더 파라미터 네이밍 개선 제안서비스 계층에서는 헤더의 의미를 일반화하여 두는 편이 유지보수에 유리합니다. tempToken 대신 authorization으로 명명하면 호출부와 역할이 더 명확해집니다(현재 호출부는 이미 "Bearer ..." 접두사를 붙여 전달 중).
아래처럼 변경을 제안합니다(호출부의 인자명 지정이 없다면 바이너리 호환성 문제는 없습니다):
- suspend fun signup( - @Header("Authorization") tempToken: String, + suspend fun signup( + @Header("Authorization") authorization: String, @Body request: SignupRequest ): BaseResponse<SignupResponse>app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt (2)
89-100: 임시 토큰 누락 시 메시지 생성의 계층 분리 제안Repository에서 사용자 문구(한글 문자열)를 생성하면 다국어/표현 변경 시 수정 범위가 커집니다. 도메인 예외를 던지고 UI 계층에서 문자열 리소스로 매핑하는 방식을 권장합니다.
아래처럼 전환을 제안드립니다:
- if (tempToken.isNullOrBlank()) { - return Result.failure(Exception("임시 토큰이 없습니다. 로그인을 다시 시도해주세요.")) - } + if (tempToken.isNullOrBlank()) { + return Result.failure(MissingTempTokenException()) + }Repository 외부(예: common/errors 패키지)에 예외 타입 추가:
class MissingTempTokenException : IllegalStateException("Temp token is missing")UI(ViewModel)에서 MissingTempTokenException을 잡아 사용자 문구를 stringResource로 매핑하세요.
102-109: 검색 size 파라미터 노출 제안서비스는 기본 30이지만, 호출부에서 사이즈 조절이 필요할 수 있습니다. Repository 메서드에 size를 옵션으로 노출하면 확장성이 좋아집니다.
아래처럼 변경을 제안합니다:
- suspend fun searchUsers( - keyword: String, - isFinalized: Boolean - ): Result<UserSearchResponse?> = runCatching { - userService.searchUsers(isFinalized = isFinalized, keyword = keyword) + suspend fun searchUsers( + keyword: String, + isFinalized: Boolean, + size: Int = 30 + ): Result<UserSearchResponse?> = runCatching { + userService.searchUsers(isFinalized = isFinalized, keyword = keyword, size = size) .handleBaseResponse() .getOrThrow() }app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt (3)
36-41: DataStore 읽기 시 IOException 캐치 없이 first() 호출 → 앱 크래시 가능성DataStore 권장 패턴대로 data 플로우에 catch를 추가해 빈 Preferences를 emit하도록 해주세요. 일시적 I/O 문제에서 안전합니다.
적용 제안(diff):
- suspend fun getTempToken(): String? { - return context.dataStore.data.map { prefs -> - prefs[TEMP_TOKEN_KEY] - }.first() // Flow에서 첫 번째 값을 한번만 읽어옴 - } + suspend fun getTempToken(): String? { + return context.dataStore.data + .catch { emit(emptyPreferences()) } + .map { prefs -> prefs[TEMP_TOKEN_KEY] } + .first() + }필요 import:
import androidx.datastore.preferences.core.emptyPreferences import kotlinx.coroutines.flow.catch
51-56: AccessToken Flow에도 예외 처리 추가 권장getAccessToken() 역시 동일한 I/O 예외 가능성이 있으니 catch를 추가해 주세요.
적용 제안(diff):
- fun getAccessToken(): kotlinx.coroutines.flow.Flow<String?> { - return context.dataStore.data.map { prefs -> - prefs[ACCESS_TOKEN_KEY] - } - } + fun getAccessToken(): kotlinx.coroutines.flow.Flow<String?> { + return context.dataStore.data + .catch { emit(emptyPreferences()) } + .map { prefs -> prefs[ACCESS_TOKEN_KEY] } + }필요 import(이미 위 코멘트에 포함): emptyPreferences, catch
51-56: Refresh Token 조회 API 부재로 대칭성/재사용성 저하Access/Refresh 동시 저장(saveAccessTokens)은 있지만 Refresh 토큰을 읽는 API가 없어 사용처에서 우회 구현될 여지가 있습니다. 대칭 API를 추가해 주세요.
참고 구현(추가 코드):
// TokenManager 내에 추가 fun getRefreshToken(): kotlinx.coroutines.flow.Flow<String?> { return context.dataStore.data .catch { emit(emptyPreferences()) } .map { prefs -> prefs[REFRESH_TOKEN_KEY] } }app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (1)
88-139: 로딩/에러 UI 미노출uiState.isLoading, uiState.errorMessage에 대한 피드백이 없어 검색 시 무반응처럼 보일 수 있습니다. 상단에 얇은 로딩 인디케이터 또는 전체 오버레이를 추가하고, 에러는 간단한 안내 텍스트/토스트로 표시하는 것을 권장합니다.
간단 예시(추가 코드):
if (uiState.isLoading) { androidx.compose.material3.LinearProgressIndicator( modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp) ) } uiState.errorMessage?.let { msg -> Spacer(Modifier.height(8.dp)) Text( text = msg, color = colors.NeonGreen, style = typography.menu_m500_s14_h24, modifier = Modifier.padding(horizontal = 20.dp) ) }app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt (4)
18-25: 최근 검색어 초기값 더미 제거 및 영속화 제안recentSearches에 하드코딩된 "메롱" 값은 프로덕션에 노출될 수 있습니다. DataStore/Room 등으로 최대 10개를 영속화해 초기 로딩하도록 전환을 권장합니다. 필요 시 스펙 제안/구현 도와드릴게요.
73-76: mock 패키지 의존 최소화toMySubscriptionData가 ui.feed.mock 아래에 있는 점이 약간 어색합니다. 실제 화면에서 사용하는 매퍼/모델이라면 ui.model(or presentation.model)로 이동해 mock 명칭 의존을 제거하면 패키지 의미가 더 명확해집니다.
37-53: 디바운스 처리 방향은 적절합니다 (추가 개선 아이디어)현재 Job+delay로 디바운스 구현이 명확합니다. 향후 입력을 Flow로 바꾸면 debounce + distinctUntilChanged + flatMapLatest로 간결하게 취소/동시성까지 일관 관리할 수 있습니다. 현 단계에서는 선택 사항입니다.
85-97: 최근 검색어 dedup/최대 개수 제한 로직 적절합니다
- 새 키워드를 맨 앞에 두고 distinct().take(10)으로 중복 제거/개수 제한하는 방식 합리적입니다.
- 추후 대소문자/공백 트리밍 규칙을 정의하면 UX가 더 좋아질 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (35)
app/build.gradle.kts(1 hunks)app/src/main/java/com/texthip/thip/MainActivity.kt(1 hunks)app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/MyFollowingsResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt(1 hunks)app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt(2 hunks)app/src/main/java/com/texthip/thip/data/service/UserService.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt(3 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt(4 hunks)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt(5 hunks)app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/navigator/extensions/MyPageNavigationExtensions.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt(1 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt(2 hunks)app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt(0 hunks)app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt(1 hunks)app/src/main/res/values/strings.xml(2 hunks)gradle/libs.versions.toml(2 hunks)
💤 Files with no reviewable changes (3)
- app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageViewModel.kt
- app/src/main/java/com/texthip/thip/ui/signin/viewmodel/NicknameViewModel.kt
- app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupAliasViewModel.kt
🧰 Additional context used
🧬 Code Graph Analysis (6)
app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt (1)
app/src/main/java/com/texthip/thip/utils/color/HexToColor.kt (1)
hexToColor(5-12)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt (2)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
DefaultTopAppBar(22-65)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt (2)
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (1)
SignupNicknameScreen(31-58)app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt (1)
SignupGenreScreen(32-60)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt (5)
app/src/main/java/com/texthip/thip/ui/common/topappbar/InputTopAppBar.kt (1)
InputTopAppBar(25-69)app/src/main/java/com/texthip/thip/ui/common/forms/WarningTextField.kt (1)
WarningTextField(35-121)app/src/main/java/com/texthip/thip/ui/mypage/component/RoleCard.kt (1)
RoleCard(34-91)app/src/main/java/com/texthip/thip/ui/common/modal/ToastWithDate.kt (1)
ToastWithDate(24-60)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (5)
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageEditScreen.kt (1)
EditProfileScreen(46-70)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (1)
SavedScreen(46-120)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt (1)
NotificationScreen(34-114)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt (1)
DeleteAccountScreen(41-169)app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageCustomerServiceScreen.kt (1)
MypageCustomerServiceScreen(21-61)
app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt (6)
app/src/main/java/com/texthip/thip/ui/common/topappbar/DefaultTopAppBar.kt (1)
DefaultTopAppBar(22-65)app/src/main/java/com/texthip/thip/ui/common/forms/SearchBookTextField.kt (1)
SearchBookTextField(38-112)app/src/main/java/com/texthip/thip/ui/feed/component/LiveSearchPeopleResult.kt (1)
SearchPeopleResult(21-50)app/src/main/java/com/texthip/thip/ui/feed/component/SearchPeopleEmptyResult.kt (1)
SearchPeopleEmptyResult(16-32)app/src/main/java/com/texthip/thip/ui/feed/component/PeopleRecentSearch.kt (1)
PeopleRecentSearch(27-63)app/src/main/java/com/texthip/thip/ui/theme/Theme.kt (1)
ThipTheme(41-69)
🔇 Additional comments (35)
app/src/main/java/com/texthip/thip/data/model/users/response/OthersFollowersResponse.kt (1)
16-21: API의 null 가능성 점검 필요(aliasName, aliasColor) — 런타임 NPE 리스크aliasName/aliasColor가 서버에서 간헐적으로 null/생략될 수 있다면:
- Gson 사용 시: Kotlin non-null(String) 필드에 null이 주입되어 런타임에서 NPE가 날 수 있습니다.
- kotlinx.serialization 사용 시: 디코딩 단계에서 실패할 수 있습니다.
API 계약이 확실히 “항상 값 존재”가 아니라면 nullable로 선언하거나 상위 레이어에서 안전하게 기본값을 부여하는 쪽이 안전합니다.
참고 변경안(nullable로 완화):
- @SerializedName("aliasName") val aliasName: String, - @SerializedName("aliasColor") val aliasColor: String, + @SerializedName("aliasName") val aliasName: String?, + @SerializedName("aliasColor") val aliasColor: String?,kotlinx.serialization로 전환 시에는 @SerialName으로 동일하게 변경하면 됩니다.
app/src/main/java/com/texthip/thip/data/model/users/request/ProfileUpdateRequest.kt (1)
5-9: 모델 자체는 깔끔합니다.요청 DTO 구조가 명확하고, 불필요한 주석/로직이 없어 유지보수에 용이합니다.
app/src/main/java/com/texthip/thip/data/model/users/request/SignupRequest.kt (1)
5-9: 요청 DTO 정의 적절.필수 필드만 포함한 심플한 모델로 충분합니다. 서버 필드명이 동일하다면 별도의 @SerialName 없이도 문제 없습니다.
app/src/main/res/values/strings.xml (1)
126-127: 문장 분리 변경 적절.2줄을 각각의 문자열 리소스로 분리하여 UI에서 줄바꿈/레이아웃 제어가 쉬워졌습니다. 연결 사용 시 접근성(읽기 순서)도 명확해집니다.
app/build.gradle.kts (1)
71-71: Datastore 의존성 추가 OK.안드로이드X Datastore Preferences 추가가 TokenManager 도입과 일치합니다.
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageNotificationEditScreen.kt (2)
73-73: 뒤로가기 콜백 연결 적절합니다TopAppBar의 좌측 클릭에
onNavigateBack을 직접 연결한 점 좋습니다. 내비게이션 일관성이 좋아졌습니다.
119-121: 프리뷰 시그니처 업데이트 적절합니다프리뷰가 새로운 파라미터 시그니처를 반영하도록 수정된 점 좋습니다.
app/src/main/java/com/texthip/thip/ui/navigator/routes/MyPageRoutes.kt (1)
12-12: 새 Route 추가 LGTM
@Serializable data object CustomerService추가로 타입-세이프 내비게이션 확장이 자연스럽습니다. 확장 함수 및 내비게이션 그래프와의 연결도 일관됩니다.app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt (1)
16-16: LaunchedEffect import 추가 문제 없음컴포저블 내 초기 사이드이펙트를 위한
LaunchedEffect사용을 위해 필요한 import입니다. 이상 없습니다.gradle/libs.versions.toml (1)
62-62: datastore-preferences 의존성 alias 추가 LGTM
TokenManager의 Preferences DataStore 사용과 일치합니다. 문제 없습니다.app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageLeavethipScreen.kt (3)
42-44: 뒤로가기 콜백 파라미터 도입 LGTM내비게이션 패턴 일관성 관점에서 좋은 변경입니다.
56-56: 탑 앱바 좌측 클릭 핸들러 연결 LGTM
onNavigateBack위임으로 사용자 기대와 맞는 동작을 합니다.
174-176: 프리뷰 업데이트 LGTM새 시그니처에 맞춘 더미 콜백 적용이 적절합니다.
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageSaveScreen.kt (4)
48-51: 뒤로가기 콜백 파라미터 추가 LGTM내비게이션 그래프와의 결합도가 낮아지고 재사용성이 좋아집니다.
65-65: 탑 앱바 좌측 클릭 핸들러 연결 LGTM사용자 기대에 맞는 뒤로가기 동작을 제공합니다.
128-129: 프리뷰 업데이트 LGTM새 시그니처에 맞춰 더미 콜백 전달이 적절합니다.
140-141: 프리뷰(빈 데이터) 업데이트 LGTM일관된 시그니처 적용 확인했습니다.
app/src/main/java/com/texthip/thip/ui/feed/mock/MySubscriptionData.kt (1)
17-26: 사용자 → 구독 UI 데이터 매핑 잘 추가됨 (LGTM)
- 필드 매핑이 명확하고, 잘못된 색상 문자열에 대한 hexToColor의 예외 처리도 커버되어 안정적입니다.
- API에 구독 여부가 없어서 isSubscribed=false로 고정한 것도 현재 제약을 고려하면 합리적입니다. 향후 API에 구독 여부가 추가되면 해당 필드로 치환하면 되겠습니다.
app/src/main/java/com/texthip/thip/ui/feed/viewmodel/FeedViewModel.kt (2)
16-16: recentWriters → myFollowingUsers로의 상태 전환 및 외부 트리거화(LGTM)
- 상태 키 변경 의도가 명확하고, 화면에서 LaunchedEffect로 호출하도록 가시화한 점 👍
- 성공/실패 모두 로딩 종료 처리도 적절합니다.
Also applies to: 29-29, 37-38
29-45: fetchRecentWriters() 호출 보장 확인 완료
FeedScreen.kt(61행)의 LaunchedEffect(Unit) 블록에서만 fetchRecentWriters()를 호출하고 있으며, 프로젝트 내 다른 호출 지점은 없습니다.app/src/main/java/com/texthip/thip/ui/navigator/navigations/signupNavigation.kt (1)
14-24: NavGraph 범위 Hilt ViewModel 공유 패턴 적절 (LGTM)
- signup_flow 단위로 hiltViewModel(parentEntry) 공유한 점과 popUpTo로 스택 정리한 점 모두 적절합니다.
Also applies to: 33-39, 39-46
app/src/main/java/com/texthip/thip/ui/navigator/navigations/MyPageNavigation.kt (1)
10-10: MyPage 고객센터 라우팅 추가 연동 적절 (LGTM)
- MyPageScreen에서 onCustomerService 콜백을 통해 navigateToCustomerService()로 자연스럽게 연결되고,
- CustomerService 라우트에서 MypageCustomerServiceScreen을 렌더링하며 뒤로가기 popBackStack도 일관됩니다.
Also applies to: 13-13, 29-31, 54-57
app/src/main/java/com/texthip/thip/ui/mypage/screen/MypageScreen.kt (1)
46-49: 고객센터 네비게이션 콜백 배선 — LGTM
- 파라미터 추가 → Content 전달 → 버튼 onClick 연결 → 프리뷰 반영까지 일관성 있게 처리되었습니다.
Also applies to: 59-64, 72-72, 166-167, 255-256
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupNicknameScreen.kt (3)
33-35: 공용 SignupViewModel 주입 및 네비게이션 콜백 — LGTM단일 ViewModel로 플로우를 공유하는 설계가 자연스럽고, 네비게이션 콜백도 명확합니다.
38-44: 장면 전환 원샷 처리 — LGTMnavigate flag를 소모(onNavigatedToGenre)하는 호출이 포함되어 재실행을 방지하는 구조가 적절합니다.
56-57: 경고 메시지 리소스 전달 — LGTMnull 처리와 stringResource 변환 시점이 안전하게 작성되었습니다.
app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt (3)
39-41: 별칭(장르/역할) 목록 초기 로드 — LGTM첫 구성에서 fetchAliasChoices() 트리거로 데이터 준비가 명확합니다.
57-59: 액션 바인딩과 중복 호출 방지 상태 — LGTMonCardSelected, onNextClick 메서드 레퍼런스 연결과 isLoading을 활용한 Button 비활성화로 중복 트리거 방지가 잘 되어 있습니다.
139-142: 프리뷰 상태 구성 — LGTM미리보기에서 선택 인덱스와 카드 목록이 직관적으로 표현되어 UI 확인이 수월합니다.
app/src/main/java/com/texthip/thip/data/service/UserService.kt (2)
59-63: PATCH users: updateProfile 엔드포인트 정의 — LGTM서버가 본문 없는 성공 응답을 주는 경우(BaseResponse)에 맞춰 선언된 것으로 보이며 문제 없습니다.
70-76: GET users 검색 API — LGTM기본 size=30 지정과 쿼리 파라미터 구성이 명확합니다.
app/src/main/java/com/texthip/thip/data/repository/UserRepository.kt (2)
24-26: TokenManager DI 추가 — LGTM회원가입 플로우에서 임시 토큰 의존성을 Repository 수준으로 명확히 둔 점이 좋습니다.
83-88: 프로필 수정 API 위임 — LGTM기존 패턴(handleBaseResponse → getOrThrow)과 일관되며 예외/성공 경로 처리가 명확합니다.
app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt (1)
104-114: 확인 완료: null 값 미포함 설정 적용되어 있습니다
NetworkModule.provideJson()에서explicitNulls = false로 설정되어 있어nickname = null일 경우 JSON에 해당 필드가 전송되지 않습니다. 의도한 대로 동작하므로 추가 조치가 필요 없습니다.app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt (1)
32-34: StateFlow 캡슐화 패턴 깔끔합니다MutableStateFlow를 private로 감추고 asStateFlow로 노출한 점 좋습니다. 이후 테스트/미들웨어 추가 시에도 유연합니다.
| @Serializable | ||
| data class SignupResponse( | ||
| @SerializedName("accessToken") val accessToken: String, | ||
| @SerializedName("userId") val userId: Long | ||
| ) No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
직렬화 어노테이션 혼용 — 한 가지로 통일 권장
@Serializable(kotlinx)와 @SerializedName(Gson)을 동시에 사용하는 것은 혼동을 야기합니다. 현재 Retrofit 컨버터 구성이 어떤지에 따라 아래 둘 중 하나로 통일하세요.
- 옵션 A) Gson만 사용 시:
@Serializable제거, Gson만 유지
-package com.texthip.thip.data.model.users.response
-
-import com.google.gson.annotations.SerializedName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class SignupResponse(
- @SerializedName("accessToken") val accessToken: String,
- @SerializedName("userId") val userId: Long
-)
+package com.texthip.thip.data.model.users.response
+
+import com.google.gson.annotations.SerializedName
+
+data class SignupResponse(
+ @SerializedName("accessToken") val accessToken: String,
+ @SerializedName("userId") val userId: Long
+)- 옵션 B) Kotlinx Serialization만 사용 시:
@SerialName으로 전환, Gson 제거
-package com.texthip.thip.data.model.users.response
-
-import com.google.gson.annotations.SerializedName
-import kotlinx.serialization.Serializable
-
-@Serializable
-data class SignupResponse(
- @SerializedName("accessToken") val accessToken: String,
- @SerializedName("userId") val userId: Long
-)
+package com.texthip.thip.data.model.users.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignupResponse(
+ @SerialName("accessToken") val accessToken: String,
+ @SerialName("userId") val userId: Long
+)Retrofit 컨버터 구성 실사용 여부를 아래 스크립트로 확인해 주세요. 결과에 따라 A/B 중 하나를 적용하면 좋습니다.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== addConverterFactory 사용 위치 ==="
rg -n -C3 --hidden 'addConverterFactory'
echo
echo "=== Gson 컨버터 사용 여부 ==="
rg -n -C3 --hidden 'GsonConverterFactory'
echo
echo "=== Kotlinx 컨버터 사용 여부 ==="
rg -n -C3 --hidden 'kotlinx\.serialization|KotlinxSerializationConverter'
echo
echo "=== 모델 어노테이션 사용 분포 ==="
rg -n --hidden '@Serializable|@SerializedName' app/src/main/java/com/texthip/thip/data/model -C2Length of output: 943
Kotlinx Serialization 컨버터 사용 중 — @SerialName으로 통일 필요
네트워크 모듈에서 json.asConverterFactory(…)를 통해 GsonConverterFactory 없이 KotlinxSerializationConverter만 설정되어 있으므로, 해당 모델에서 Gson 어노테이션(@SerializedName)을 제거하고 Kotlinx 어노테이션(@SerialName)으로 전환하세요.
적용 위치:
- app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt
@@ app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt
-import com.google.gson.annotations.SerializedName
-import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerialName
@Serializable
data class SignupResponse(
- @SerializedName("accessToken") val accessToken: String,
- @SerializedName("userId") val userId: Long
+ @SerialName("accessToken") val accessToken: String,
+ @SerialName("userId") val userId: Long
)📝 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.
| @Serializable | |
| data class SignupResponse( | |
| @SerializedName("accessToken") val accessToken: String, | |
| @SerializedName("userId") val userId: Long | |
| ) | |
| import kotlinx.serialization.Serializable | |
| import kotlinx.serialization.SerialName | |
| @Serializable | |
| data class SignupResponse( | |
| @SerialName("accessToken") val accessToken: String, | |
| @SerialName("userId") val userId: Long | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/users/response/SignupResponse.kt
around lines 6 to 10, replace the Gson annotation usages with Kotlinx
Serialization ones: remove @SerializedName and annotate properties with
@SerialName("accessToken") and @SerialName("userId"); ensure the file imports
kotlinx.serialization.SerialName and retains @Serializable on the data class so
the kotlinx serialization converter can map JSON fields correctly.
| import com.google.gson.annotations.SerializedName | ||
| import kotlinx.serialization.Serializable |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Gson 주석과 kotlinx.serialization 혼용 — 한 가지 직렬화 전략으로 통일 권장.
현재 @serializable(kotlinx)와 @SerializedName(Gson)을 혼용 중입니다. Retrofit에서 GsonConverterFactory와 KotlinxSerializationConverterFactory를 함께 쓰면, 등록 순서에 따라 서로 다른 파서가 선택되어 예기치 않은 동작/버그가 발생할 수 있습니다. 해당 응답 모델을 Kotlinx로 사용할 계획이라면 @SerialName으로 통일하고 Gson 주석과 import를 제거해주세요. 반대로 Gson을 쓸 계획이면 @serializable를 제거하세요.
권장 변경(Kotlinx로 통일 시):
-import com.google.gson.annotations.SerializedName
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable @Serializable
data class UserItem(
- @SerializedName("userId") val userId: Int,
- @SerializedName("nickname") val nickname: String,
- @SerializedName("profileImageUrl") val profileImageUrl: String?,
- @SerializedName("aliasName") val aliasName: String,
- @SerializedName("aliasColor") val aliasColor: String,
- @SerializedName("followerCount") val followerCount: Int
+ @SerialName("userId") val userId: Long,
+ @SerialName("nickname") val nickname: String,
+ @SerialName("profileImageUrl") val profileImageUrl: String?,
+ @SerialName("aliasName") val aliasName: String,
+ @SerialName("aliasColor") val aliasColor: String,
+ @SerialName("followerCount") val followerCount: Int
)Retrofit 구성 시 ConverterFactory 순서도 확인이 필요합니다(Gson 제거 또는 Kotlinx 우선 등록).
📝 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.
| import com.google.gson.annotations.SerializedName | |
| import kotlinx.serialization.Serializable | |
| // app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt | |
| import kotlinx.serialization.SerialName | |
| import kotlinx.serialization.Serializable | |
| @Serializable | |
| data class UserItem( | |
| @SerialName("userId") val userId: Long, | |
| @SerialName("nickname") val nickname: String, | |
| @SerialName("profileImageUrl") val profileImageUrl: String?, | |
| @SerialName("aliasName") val aliasName: String, | |
| @SerialName("aliasColor") val aliasColor: String, | |
| @SerialName("followerCount") val followerCount: Int | |
| ) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt
around lines 3-4, the file mixes Gson (@SerializedName) and
kotlinx.serialization (@Serializable) annotations causing ambiguous parser
selection; standardize to kotlinx.serialization by removing the Gson import and
any @SerializedName usages, replace them with @SerialName where needed, keep
@Serializable on the data class, and then verify Retrofit converter registration
removes GsonConverterFactory or registers the kotlinx converter with higher
priority.
| @SerializedName("userId") val userId: Int, | ||
| @SerializedName("nickname") val nickname: String, | ||
| @SerializedName("profileImageUrl") val profileImageUrl: String?, | ||
| @SerializedName("aliasName") val aliasName: String, | ||
| @SerializedName("aliasColor") val aliasColor: String, | ||
| @SerializedName("followerCount") val followerCount: Int | ||
| ) No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🛠️ Refactor suggestion
🧩 Analysis chain
userId 타입 불일치(Int vs Long) — 도메인 전반 Long으로 일원화 권장.
SignupResponse에서 userId: Long으로 정의된 반면, 여기서는 Int입니다. 사용자 식별자는 장기적으로 Int 범위를 초과할 가능성이 있고, 타입 혼재는 변환/비교 시 미묘한 버그를 유발합니다. 전역적으로 Long으로 통일하는 것을 권장합니다.
다음 스크립트로 userId 타입 불일치 지점을 점검할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# userId 타입 선언 위치와 사용처 탐색
rg -n -C2 -P 'data\s+class\s+\w+\s*\(.*\buserId:\s*(Int|Long)' app | sed 's/^/DECL: /'
rg -n -C2 -P '\buserId\s*:\s*(Int|Long)\b' app | sed 's/^/TYPE: /'
rg -n -C2 -P '\buserId\b' app/src/main/java | sed 's/^/USE: /'Length of output: 44260
userId 타입 불일치(Int vs Long) — 도메인 전반 Long으로 통일 필요
현재 아래 모델 및 DTO에서 userId가 Int로 정의되어 있지만, 다른 곳은 모두 Long을 사용하고 있어 타입 혼재로 인한 변환·비교 버그 위험과 향후 32비트 범위 초과 가능성이 있습니다. 전역적으로 Long으로 일원화해 주세요.
수정이 필요한 주요 파일:
- app/src/main/java/com/texthip/thip/data/model/users/response/UserSearchResponse.kt
- app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsUsersResponse.kt
- app/src/main/java/com/texthip/thip/data/model/rooms/response/RoomsPostsResponse.kt
(필요 시 테스트·모킹용 클래스에서도 userId: Int → userId: Long으로 변경)
예시:
data class UserItem(
- @SerializedName("userId") val userId: Int,
+ @SerializedName("userId") val userId: Long,
@SerializedName("nickname") val nickname: String,
...
)— 변경 후 앱 전반의 사용자 식별자 타입이 일관된지, 호출부·뷰모델에서 Long으로 사용되는지 확인해 주세요.
Committable suggestion skipped: line range outside the PR's diff.
| @Composable | ||
| fun SearchPeopleContent( | ||
| uiState: SearchPeopleUiState, | ||
| onSearchTextChanged: (String) -> Unit, | ||
| onFinalSearch: (String) -> Unit, | ||
| onRecentSearchClick: (String) -> Unit, | ||
| onRecentSearchRemove: (String) -> Unit | ||
|
|
||
| ) { | ||
|
|
||
| Box( | ||
| modifier = modifier.fillMaxSize() | ||
| Column( | ||
| modifier = Modifier.fillMaxSize() | ||
| ) { | ||
| Column( | ||
| modifier = Modifier.fillMaxSize() | ||
| ) { | ||
| DefaultTopAppBar( | ||
| title = stringResource(R.string.search_user), | ||
| onLeftClick = {}, | ||
| ) | ||
| Column( | ||
| modifier = Modifier | ||
| .fillMaxSize() | ||
| ) { | ||
| Spacer(modifier = Modifier.height(16.dp)) | ||
|
|
||
| SearchBookTextField( | ||
| DefaultTopAppBar( | ||
| title = stringResource(R.string.search_user), | ||
| onLeftClick = {}, | ||
| ) | ||
| Spacer(modifier = Modifier.height(16.dp)) |
There was a problem hiding this comment.
상단 백 아이콘이 동작하지 않음(onLeftClick = {})
사용자가 뒤로가기를 눌러도 아무 동작이 없어 UX 결함입니다. 시스템 back dispatcher를 연결해 즉시 해결 가능합니다.
적용 제안(diff):
@Composable
fun SearchPeopleContent(
uiState: SearchPeopleUiState,
onSearchTextChanged: (String) -> Unit,
onFinalSearch: (String) -> Unit,
onRecentSearchClick: (String) -> Unit,
onRecentSearchRemove: (String) -> Unit
) {
+ val backDispatcher = androidx.activity.compose.LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
@@
- DefaultTopAppBar(
+ DefaultTopAppBar(
title = stringResource(R.string.search_user),
- onLeftClick = {},
+ onLeftClick = { backDispatcher?.onBackPressed() },
)권장 import:
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/screen/SearchPeopleScreen.kt
around lines 58 to 75, the top-app-bar left/back icon handler is a no-op
(onLeftClick = {}) so the back button doesn't navigate; fix by wiring the
onLeftClick to the system back dispatcher: obtain the dispatcher via
LocalOnBackPressedDispatcherOwner.current and invoke its
onBackPressedDispatcher.onBackPressed() inside onLeftClick, and add the
recommended import (import
androidx.activity.compose.LocalOnBackPressedDispatcherOwner) at the top of the
file.
| searchJob = viewModelScope.launch { | ||
| delay(500L) | ||
| searchUsers(keyword = text, isFinalized = false) | ||
| } | ||
| } else { |
There was a problem hiding this comment.
검색 취소/동시 호출 레이스로 인해 오래된 결과가 최신 결과를 덮어쓸 수 있음 — searchUsers를 suspend로 바꾸고 단일 Job으로 관리하세요
현재 searchUsers 내부에서 viewModelScope.launch를 다시 생성하고 있어(이중 launch), 외부에서 관리하는 searchJob 취소가 네트워크 호출에 전달되지 않습니다. 그 결과:
- 빠른 타이핑/연속 검색 시 이전 호출이 취소되지 않고 동시 실행됨
- 오래된 응답이 나중에 도착해 최신 쿼리의 결과를 덮어쓰는 레이스 발생
- isLoading 토글이 응답 순서에 따라 틀어질 수 있음
해결책:
- searchUsers를 suspend로 변경해 호출한 코루틴(searchJob)의 취소가 전파되도록 합니다.
- onFinalSearch도 searchJob에서 실행해 최종 검색 역시 동일 취소/순서 보장이 되도록 통일합니다.
- 요청 시작 시 errorMessage를 null로 초기화하고, 응답 반영 시 현재 쿼리와 일치할 때만 상태를 갱신(스테일 드롭)합니다.
아래 패치를 제안합니다.
@@
- // 키보드의 '검색' 버튼이나 아이콘을 눌렀을 때 호출
- fun onFinalSearch(query: String) {
- searchJob?.cancel()
- _uiState.update { it.copy(isSearched = true) }
-
- if (query.isNotBlank()) {
- addRecentSearch(query)
- searchUsers(keyword = query, isFinalized = true)
- } else {
- _uiState.update { it.copy(searchResults = emptyList()) }
- }
- }
+ // 키보드의 '검색' 버튼이나 아이콘을 눌렀을 때 호출
+ fun onFinalSearch(query: String) {
+ searchJob?.cancel()
+ searchJob = viewModelScope.launch {
+ _uiState.update { it.copy(isSearched = true, searchText = query) }
+ if (query.isNotBlank()) {
+ addRecentSearch(query)
+ searchUsers(keyword = query, isFinalized = true)
+ } else {
+ _uiState.update { it.copy(searchResults = emptyList()) }
+ }
+ }
+ }
@@
- // 실제 API를 호출하는 private 함수
- private fun searchUsers(keyword: String, isFinalized: Boolean) {
- viewModelScope.launch {
- _uiState.update { it.copy(isLoading = true) }
- userRepository.searchUsers(keyword, isFinalized)
- .onSuccess { response ->
- val userList =
- response?.userList?.map { it.toMySubscriptionData() } ?: emptyList()
- _uiState.update { it.copy(isLoading = false, searchResults = userList) }
- }
- .onFailure { exception ->
- _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) }
- }
- }
- }
+ // 실제 API를 호출하는 private suspend 함수 (호출자의 Job 취소가 전파됨)
+ private suspend fun searchUsers(keyword: String, isFinalized: Boolean) {
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+ userRepository.searchUsers(keyword, isFinalized)
+ .onSuccess { response ->
+ val userList = response?.userList?.map { it.toMySubscriptionData() } ?: emptyList()
+ // 중간 검색(debounce) 동안의 스테일 응답은 드롭
+ val shouldApply = isFinalized || keyword == uiState.value.searchText
+ if (shouldApply) {
+ _uiState.update { it.copy(isLoading = false, searchResults = userList) }
+ }
+ }
+ .onFailure { exception ->
+ val shouldApply = isFinalized || keyword == uiState.value.searchText
+ if (shouldApply) {
+ _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) }
+ }
+ }
+ }이렇게 하면 단일 searchJob으로 모든 검색을 관리하므로, 취소 전파/순서 보장이 되고 UI가 과거 응답으로 오염되지 않습니다.
Also applies to: 56-66, 68-82
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/feed/viewmodel/SearchPeopleViewModel.kt
around lines 45-49 (also apply same changes to 56-66 and 68-82), replace the
current pattern that launches a nested coroutine inside searchUsers with a
suspend searchUsers so cancellation propagates: make searchUsers a suspend
function and remove its internal viewModelScope.launch; ensure you cancel any
existing searchJob before assigning a new viewModelScope.launch for the
debounced search (so only one Job runs), run onFinalSearch inside that same
searchJob, set errorMessage = null at the start of each request, and when
applying results only update state if the current query equals the keyword
passed (drop stale responses); wrap the network call in try/catch/finally to set
isLoading properly and rethrow/capture cancellation so isLoading is consistent.
| val request = ProfileUpdateRequest( | ||
| nickname = nicknameToSend, | ||
| aliasName = selectedRole.genre | ||
| ) |
There was a problem hiding this comment.
프로필 업데이트 요청 시 aliasName 잘못 전송 가능성
위 매핑 정정 이후에는 aliasName=selectedRole.role이 맞습니다. 현재는 genre를 사용하고 있습니다.
적용 제안(diff):
- val request = ProfileUpdateRequest(
- nickname = nicknameToSend,
- aliasName = selectedRole.genre
- )
+ val request = ProfileUpdateRequest(
+ nickname = nicknameToSend,
+ aliasName = selectedRole.role
+ )📝 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.
| val request = ProfileUpdateRequest( | |
| nickname = nicknameToSend, | |
| aliasName = selectedRole.genre | |
| ) | |
| val request = ProfileUpdateRequest( | |
| nickname = nicknameToSend, | |
| aliasName = selectedRole.role | |
| ) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/mypage/viewmodel/MyPageEditViewModel.kt
around lines 111 to 114, the ProfileUpdateRequest currently uses aliasName =
selectedRole.genre but should send aliasName = selectedRole.role; update that
field mapping so aliasName uses selectedRole.role when constructing the
ProfileUpdateRequest.
| LaunchedEffect(uiState.isSignupSuccess) { | ||
| if (uiState.isSignupSuccess) { | ||
| onSignupSuccess() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
isSignupSuccess 플래그 미소모 시 재네비게이션 가능성
LaunchedEffect는 key 값(true) 상태에서 화면 재입장 시 다시 실행될 수 있습니다. Nickname 화면과 동일하게 성공 이벤트를 소모(reset)하는 호출이 필요합니다(예: onSignupSuccessConsumed()).
아래처럼 후처리를 추가하는 것을 제안합니다(해당 ViewModel 메서드 구현 필요):
LaunchedEffect(uiState.isSignupSuccess) {
if (uiState.isSignupSuccess) {
onSignupSuccess()
+ viewModel.onSignupSuccessConsumed()
}
}📝 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.
| LaunchedEffect(uiState.isSignupSuccess) { | |
| if (uiState.isSignupSuccess) { | |
| onSignupSuccess() | |
| } | |
| } | |
| LaunchedEffect(uiState.isSignupSuccess) { | |
| if (uiState.isSignupSuccess) { | |
| onSignupSuccess() | |
| viewModel.onSignupSuccessConsumed() | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/screen/SignupGenreScreen.kt
around lines 42–47, the LaunchedEffect reacts to uiState.isSignupSuccess but
does not consume/reset the success flag, which can cause re-navigation when the
composable re-enters; after calling onSignupSuccess(), call a ViewModel consumer
method (e.g., onSignupSuccessConsumed()) to reset the flag in the ViewModel, and
implement that method to clear the success state (or emit a one-shot event) so
the LaunchedEffect does not retrigger on recomposition or navigation back.
| fun fetchAliasChoices() { | ||
| viewModelScope.launch { | ||
| _uiState.update { it.copy(isLoading = true) } | ||
| userRepository.getAliasChoices() | ||
| .onSuccess { response -> | ||
| val roleCards = response?.aliasChoices?.map { RoleItem(it.aliasName, it.categoryName, it.imageUrl, it.aliasColor) } ?: emptyList() | ||
| _uiState.update { it.copy(isLoading = false, roleCards = roleCards) } | ||
| } | ||
| .onFailure { exception -> _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } } | ||
| } | ||
| } |
There was a problem hiding this comment.
RoleItem 매핑 필드가 뒤바뀜(카테고리/칭호 혼동) → UI 라벨 반전
RoleCard의 파라미터 의미는 genre=카테고리명, role=칭호(별칭)입니다. 현재 aliasChoices 매핑이 aliasName→genre, categoryName→role로 반대로 들어가 UI 표기가 뒤집힙니다.
적용 제안(diff):
- val roleCards = response?.aliasChoices?.map { RoleItem(it.aliasName, it.categoryName, it.imageUrl, it.aliasColor) } ?: emptyList()
+ val roleCards = response?.aliasChoices
+ ?.map { RoleItem(it.categoryName, it.aliasName, it.imageUrl, it.aliasColor) }
+ ?: emptyList()📝 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.
| fun fetchAliasChoices() { | |
| viewModelScope.launch { | |
| _uiState.update { it.copy(isLoading = true) } | |
| userRepository.getAliasChoices() | |
| .onSuccess { response -> | |
| val roleCards = response?.aliasChoices?.map { RoleItem(it.aliasName, it.categoryName, it.imageUrl, it.aliasColor) } ?: emptyList() | |
| _uiState.update { it.copy(isLoading = false, roleCards = roleCards) } | |
| } | |
| .onFailure { exception -> _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } } | |
| } | |
| } | |
| fun fetchAliasChoices() { | |
| viewModelScope.launch { | |
| _uiState.update { it.copy(isLoading = true) } | |
| userRepository.getAliasChoices() | |
| .onSuccess { response -> | |
| val roleCards = response?.aliasChoices | |
| ?.map { RoleItem(it.categoryName, it.aliasName, it.imageUrl, it.aliasColor) } | |
| ?: emptyList() | |
| _uiState.update { it.copy(isLoading = false, roleCards = roleCards) } | |
| } | |
| .onFailure { exception -> | |
| _uiState.update { it.copy(isLoading = false, errorMessage = exception.message) } | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt
around lines 69–79, the mapping of aliasChoices into RoleItem is reversed
(aliasName and categoryName are swapped), causing UI labels to be inverted;
change the mapping so RoleItem receives genre = categoryName and role =
aliasName (keep imageUrl and aliasColor as-is), i.e. construct
RoleItem(categoryName, aliasName, imageUrl, aliasColor) and update the _uiState
assignment accordingly.
| val request = SignupRequest( | ||
| nickname = currentState.nickname, | ||
| aliasName = selectedRole.genre | ||
| ) | ||
|
|
There was a problem hiding this comment.
회원가입 요청 시 aliasName 필드에 잘못된 값 사용 가능성
위 매핑 수정 이후에는 요청에 aliasName=selectedRole.role을 사용해야 실제 서버가 기대하는 "칭호(별칭)"가 전송됩니다. 현재는 genre를 사용하고 있어 카테고리명이 전송될 수 있습니다.
적용 제안(diff):
- val request = SignupRequest(
- nickname = currentState.nickname,
- aliasName = selectedRole.genre
- )
+ val request = SignupRequest(
+ nickname = currentState.nickname,
+ aliasName = selectedRole.role
+ )📝 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.
| val request = SignupRequest( | |
| nickname = currentState.nickname, | |
| aliasName = selectedRole.genre | |
| ) | |
| val request = SignupRequest( | |
| nickname = currentState.nickname, | |
| aliasName = selectedRole.role | |
| ) |
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ui/signin/viewmodel/SignupViewModel.kt
around lines 95 to 99, the SignupRequest currently assigns aliasName =
selectedRole.genre which can send a category name instead of the server-expected
title/alias; change the mapping to aliasName = selectedRole.role so the correct
"칭호(별칭)" value is sent in the request and update any related variable names or
tests to reflect this corrected field.
| retrofit = "3.0.0" | ||
| retrofitKotlinSerializationConverter = "1.0.0" | ||
| androidxComposeNavigation = "2.8.2" | ||
| datastorePreferences = "1.1.7" |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
DataStore 버전 유효성 확인 필요
datastorePreferences = "1.1.7"은 비교적 이례적으로 높은 마이너 버전입니다. 실제 배포 버전인지 확인해 주세요. 존재하지 않는 버전을 지정하면 동기화/빌드가 실패합니다.
다음 스크립트를 실행해 Maven Central에 배포된 버전 목록에서 1.1.7 존재 여부를 확인해 주세요:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# androidx.datastore:datastore-preferences 버전 목록 조회
curl -s 'https://search.maven.org/solrsearch/select?q=g:androidx.datastore+AND+a:datastore-preferences&rows=200&wt=json' \
| jq -r '.response.docs[].v' | sort -V
# 특정 버전 존재 여부 확인
TARGET="1.1.7"
if curl -sfI "https://repo1.maven.org/maven2/androidx/datastore/datastore-preferences/${TARGET}/datastore-preferences-${TARGET}.pom" >/dev/null; then
echo "FOUND: $TARGET"
else
echo "NOT FOUND: $TARGET"
fiLength of output: 361
datastore-preferences 버전 존재하지 않음 – 유효한 버전으로 수정 필요
Maven Central에 1.1.7 버전이 존재하지 않아 현재 빌드/동기화가 실패합니다. 실제 배포된 최신 버전을 확인한 뒤 gradle/libs.versions.toml의 해당 항목을 올바른 버전으로 수정해주세요.
- File:
gradle/libs.versions.toml, Line 28- datastorePreferences = "1.1.7" + datastorePreferences = "<실제_배포된_버전>"
📝 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.
| datastorePreferences = "1.1.7" | |
| datastorePreferences = "<실제_배포된_버전>" |
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around line 28, the datastorePreferences entry is
set to the non-existent version "1.1.7"; check Maven Central (or the official
artifact repo) for the actual published latest version of
androidx.datastore:datastore-preferences (or matching artifact used in the
project) and update the value on line 28 to that valid version string, then
re-sync the Gradle project to verify the build succeeds.
➕ 이슈 링크
🔎 작업 내용
회원가입같은 경우는 아직 소셜로그인 코드가 머지되지 않아 헤더로 보내야하는 임시토큰을 넣을 수 없는 상황이었습니다.
따라서 임의의 존재하지 않는 임시토큰 값을 넣어 "유효하지 않은 토큰입니다" 라는 에러를 받아오는 것까지 확인한 상태입니다. 참고해주세요..!
📸 스크린샷
😢 해결하지 못한 과제
프로필 편집에서 텍스트필드에 오류 메세지를 서버에서 보내주는 메시지를 파싱하여 띄우려고 하는데, 오류가 뜨지 않고 로그로만 나오는 문제를 아직 해결하지 못한 상태입니다. 해당 부분 금방 해결해보도록 하겠습니다,
📢 리뷰어들에게
Summary by CodeRabbit