Feat/#5 mypage screen구현#36
Hidden character warning
Conversation
# Conflicts: # feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt # feature/main/src/main/java/com/umcspot/spot/main/MainNavigator.kt
Walkthrough마이페이지 기능을 새로 추가하고 관련 디자인 시스템, 위치 유틸리티, 데이터/도메인/서비스 계층을 확장했습니다. API 엔드포인트·DTO·매퍼·리포지토리 메서드·ViewModel·Compose UI·네비게이션과 일부 디자인 컴포넌트/유틸 함수가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant UI as MyPageScreen
participant VM as MyPageViewModel
participant Repo as UserRepository
participant DS as UserDataSource
participant API as UserService
participant Assets as Location Assets
User->>UI: MyPage 진입
UI->>VM: load()
VM->>VM: 병렬 로드 시작
par 회원 정보
VM->>Repo: getMyPageInfo()
Repo->>DS: getMyPageInfo()
DS->>API: GET /api/members/info
API-->>DS: MyPageResponseDto
DS-->>Repo: BaseResponse<MyPageResponseDto>
Repo->>Repo: mapper.toDomain() -> MyPageResult
Repo-->>VM: Result<MyPageResult>
and 선호 지역
VM->>Repo: getUserPreferredRegionName()
Repo->>Assets: load locations asset
Repo->>Repo: mapRegionCodesToFullNames(codes, locations)
Repo-->>VM: Result<List<String?>>
and 선호 카테고리
VM->>Repo: getUserPreferredCategory()
Repo->>DS: getUserPreferredCategory()
DS->>API: GET /api/members/prefer-categories
API-->>DS: UserPreferredCategoryResponseDto
DS-->>Repo: BaseResponse<UserPreferredCategoryResponseDto>
Repo->>Repo: mapper.toDomain()
Repo-->>VM: Result<UserPreferredCategoryResult>
end
VM-->>UI: uiState 업데이트
UI->>User: 화면 렌더링
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
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: 9
🤖 Fix all issues with AI agents
In `@core/common/src/main/java/com/umcspot/spot/common/location/Location.kt`:
- Around line 85-94: The current mapRegionCodesToFullNames uses mapNotNull which
drops any regionCodes not found in locations (via toCodeMap), causing length
mismatch; update mapRegionCodesToFullNames to iterate regionCodes with map (not
mapNotNull) and return a placeholder for missing entries (e.g., the original
code or an empty string) by using the lookup map[code]?.fullName ?: code (or
another agreed placeholder) so the output list preserves the same length and
order as the input.
In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt`:
- Around line 98-105: The from(value: String) factory in Global.kt currently
lowercases the input with value.lowercase() and defaults unknown values to
KAKAO, causing misclassification; change it to use value.lowercase(Locale.ROOT)
and stop defaulting to KAKAO—either return a nullable SocialLoginType? or add an
UNKNOWN enum constant and return that; then update callers such as UserMapper.kt
(the loginType mapping) to handle the nullable/UNKNOWN case explicitly instead
of silently treating it as KAKAO.
- Around line 63-68: StudyTheme.from currently only matches enum.name exactly;
make it consistent with SocialLoginType.from by normalizing input and matching
both the enum name and its title case-insensitively. Update the StudyTheme.from
function to trim and case-normalize the incoming value and return the first
matching enum where value equals name (ignore case) or equals title (ignore
case) — keep the method on the StudyTheme companion object and follow the same
matching logic used in SocialLoginType.from.
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`:
- Around line 491-499: In InterestedInfo, the nullable entries in interest can
produce "#null" when joined; update the logic that builds interestText to filter
out nulls (and optionally blank strings) before joining so only non-null values
are prefixed with "#" — for example, use interest.filterNotNull().filter {
it.isNotBlank() }.joinToString(" ") { "#$it" } inside the remember block so
InterestedInfo produces no "#null" tokens.
- Around line 186-231: The InterestedInfo composables are using empty lambdas so
the passed-in handlers aren't wired; replace the onClick = {} for the "관심 분야"
InterestedInfo with the provided onEditInterestClick and the onClick = {} for
the "관심 지역" InterestedInfo with onEditInterestLocationClick (using the same
parameter names from the parent) so the click handlers are invoked; ensure you
reference the InterestedInfo usage lines and preserve the existing data passed
as interest.
- Around line 686-705: The AppVersion Composable currently hardcodes "v1.0.0";
replace that with a dynamic version source. Prefer adding versionName to
BuildConfig via your existing BuildConfigFieldProvider pattern (follow other
modules) so AppVersion reads BuildConfig.VERSION_NAME; alternatively fetch at
runtime using Context.packageManager.getPackageInfo() (or
PackageManager.getPackageInfoCompat) inside AppVersion or its caller; or add
buildConfigField VERSION_NAME in build-logic/convention/BuildConfig.kt and
access it from the module. Update references in AppVersion to use the chosen
dynamic value instead of the literal "v1.0.0".
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`:
- Around line 28-39: The onFailure branch in loadMemberInfo currently only logs
the error and leaves _uiState.memberInfo as UiState.Loading; update the
onFailure handler to set _uiState.update { it.copy(memberInfo =
UiState.Failure(e.message ?: "회원 정보 불러오기 실패")) } (or a localized message) so
memberInfo moves to Failure; modify the onFailure lambda where
userRepository.getMyPageInfo() is called to perform this _uiState update and
keep the Log.e call.
- Around line 43-71: In loadPrefer(), after calling
userRepository.getUserPreferredRegionName() and getUserPreferredCategory()
inside the viewModelScope.launch blocks, set the corresponding _uiState fields
(preferRegions and preferCategories) to UiState.Failure with a useful message in
each onFailure handler instead of only logging; update the onFailure for
getUserPreferredRegionName() to call _uiState.update { it.copy(preferRegions =
UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) } and similarly update the
onFailure for getUserPreferredCategory() to call _uiState.update {
it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) } so
the UI leaves the Loading state on errors.
🧹 Nitpick comments (1)
data/user/src/main/java/com/umcspot/spot/user/mapper/UserMapper.kt (1)
54-57: 알 수 없는 카테고리 코드가 null로 남는 점 개선 권장.Line 56에서
StudyTheme.from()가 null을 반환하면 리스트에 null이 포함되어 UI에서 빈 항목/예외로 이어질 수 있습니다. 최소한 원문 코드로 fallback 하거나 필터링하는 쪽이 안전합니다.✅ 제안 수정안
fun UserPreferredCategoryResponseDto.toDomain(): UserPreferredCategoryResult = UserPreferredCategoryResult( - categories = categories.map { StudyTheme.from(it)?.title }, + categories = categories.map { StudyTheme.from(it)?.title ?: it }, totalCount = totalCount )
| fun List<LocationRow>.toCodeMap(): Map<String, LocationRow> = | ||
| associateBy { it.code } | ||
|
|
||
| fun mapRegionCodesToFullNames( | ||
| regionCodes: List<String>, | ||
| locations: List<LocationRow> | ||
| ): List<String> { | ||
| val map = locations.toCodeMap() | ||
| return regionCodes.mapNotNull { code -> map[code]?.fullName } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and read the Location.kt file to understand the full context
find . -type f -name "Location.kt" | head -5Repository: SPOTeam/Android-V2
Length of output: 134
🏁 Script executed:
# Search for usages of mapRegionCodesToFullNames to understand how it's used
rg "mapRegionCodesToFullNames" --type kotlin -B 3 -A 3Repository: SPOTeam/Android-V2
Length of output: 1662
🏁 Script executed:
# Search for LocationRow definition to understand its structure
rg "class LocationRow|data class LocationRow" --type kotlin -B 2 -A 5Repository: SPOTeam/Android-V2
Length of output: 794
🏁 Script executed:
# Look for any tests related to this function
rg "mapRegionCodesToFullNames|toCodeMap" --type kotlin -B 2 -A 5Repository: SPOTeam/Android-V2
Length of output: 2273
🏁 Script executed:
# Read the full Location.kt file to see the complete LocationRow definition and any extensions
cat -n ./core/common/src/main/java/com/umcspot/spot/common/location/Location.ktRepository: SPOTeam/Android-V2
Length of output: 3564
🏁 Script executed:
# Search for fullName in the codebase to understand where it comes from
rg "fullName" --type kotlin -B 2 -A 2Repository: SPOTeam/Android-V2
Length of output: 4301
🏁 Script executed:
# Find usages of mapRegionCodesToFullNames in detail
rg "mapRegionCodesToFullNames" --type kotlin -B 5 -A 10Repository: SPOTeam/Android-V2
Length of output: 3296
🏁 Script executed:
# Check UserRepositoryImpl for the specific usage context
cat -n ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.kt | grep -A 20 "mapRegionCodesToFullNames"Repository: SPOTeam/Android-V2
Length of output: 1199
🏁 Script executed:
# Search for actual usage of mapRegionCodesToFullNames function call
rg "mapRegionCodesToFullNames\s*\(" --type kotlin -B 10 -A 5Repository: SPOTeam/Android-V2
Length of output: 1645
🏁 Script executed:
# Let's check if mapRegionCodesToFullNames is actually being used anywhere
rg "mapRegionCodesToFullNames" --type kotlin | grep -v "fun mapRegionCodesToFullNames" | grep -v "import"Repository: SPOTeam/Android-V2
Length of output: 44
🏁 Script executed:
# Check the full UserRepositoryImpl.kt file for any usage of mapRegionCodesToFullNames
wc -l ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.ktRepository: SPOTeam/Android-V2
Length of output: 149
🏁 Script executed:
# Read the full file
cat -n ./data/user/src/main/java/com/umcspot/spot/user/repositoryimpl/UserRepositoryImpl.ktRepository: SPOTeam/Android-V2
Length of output: 4086
mapNotNull로 인해 존재하지 않는 코드가 자동으로 필터링됩니다.
입력 regionCodes에 포함된 코드가 locations에 없으면 결과에서 제외되어 길이 불일치가 발생할 수 있습니다. UI가 동일 길이를 기대하거나 데이터 무결성이 중요하다면 누락된 코드를 보존하는 방식이 더 안전합니다.
길이 보존 예시
- return regionCodes.mapNotNull { code -> map[code]?.fullName }
+ return regionCodes.map { code -> map[code]?.fullName ?: code }🤖 Prompt for AI Agents
In `@core/common/src/main/java/com/umcspot/spot/common/location/Location.kt`
around lines 85 - 94, The current mapRegionCodesToFullNames uses mapNotNull
which drops any regionCodes not found in locations (via toCodeMap), causing
length mismatch; update mapRegionCodesToFullNames to iterate regionCodes with
map (not mapNotNull) and return a placeholder for missing entries (e.g., the
original code or an empty string) by using the lookup map[code]?.fullName ?:
code (or another agreed placeholder) so the output list preserves the same
length and order as the input.
| OTHER("기타"); | ||
|
|
||
| companion object { | ||
| fun from(value: String): StudyTheme? = | ||
| values().firstOrNull { it.name == value } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and read the Global.kt file
fd "Global.kt" --type fRepository: SPOTeam/Android-V2
Length of output: 119
🏁 Script executed:
# Also get size to determine how to read
wc -l core/model/src/main/java/com/umcspot/spot/model/Global.ktRepository: SPOTeam/Android-V2
Length of output: 123
🏁 Script executed:
cat -n core/model/src/main/java/com/umcspot/spot/model/Global.ktRepository: SPOTeam/Android-V2
Length of output: 3038
🏁 Script executed:
# Search for usages of StudyTheme.from
rg "StudyTheme\.from" --type kt -B 2 -A 2Repository: SPOTeam/Android-V2
Length of output: 88
🏁 Script executed:
# Search for API models and responses that might use StudyTheme
rg "StudyTheme" --type kt -lRepository: SPOTeam/Android-V2
Length of output: 88
🏁 Script executed:
# Search for StudyTheme.from usages without type filter
rg "StudyTheme\.from" -B 2 -A 2Repository: SPOTeam/Android-V2
Length of output: 614
🏁 Script executed:
# Search for all StudyTheme references to understand usage patterns
rg "StudyTheme" --include="*.kt" -lRepository: SPOTeam/Android-V2
Length of output: 503
🏁 Script executed:
# Search for all StudyTheme references
rg "StudyTheme"Repository: SPOTeam/Android-V2
Length of output: 13899
🏁 Script executed:
# Find UserPreferredCategoryResponseDto to see API contract
rg "UserPreferredCategoryResponseDto" -B 5 -A 10Repository: SPOTeam/Android-V2
Length of output: 12643
🏁 Script executed:
# Search for test files related to UserPreferredCategoryResponseDto or StudyTheme conversion
find . -name "*Test.kt" -o -name "*test.kt" | head -20Repository: SPOTeam/Android-V2
Length of output: 188
🏁 Script executed:
# Search for test data or fixtures showing API response format
rg "UserPreferredCategoryResponseDto\|categories.*=" -A 3 -B 1Repository: SPOTeam/Android-V2
Length of output: 44
🏁 Script executed:
# Search for API response examples or mock data
rg "categories.*LANGUAGE\|categories.*Language\|categories.*language" -B 2 -A 2Repository: SPOTeam/Android-V2
Length of output: 44
🏁 Script executed:
# Check how other enums handle from() conversions
rg "fun from\(" --include="*.kt" -B 2 -A 5Repository: SPOTeam/Android-V2
Length of output: 503
🏁 Script executed:
# Search for from() function patterns in all files
rg "fun from\(" -B 2 -A 5Repository: SPOTeam/Android-V2
Length of output: 1377
🏁 Script executed:
# Let's check the actual API response DTO more carefully
cat -n data/user/src/main/java/com/umcspot/spot/user/dto/response/UserPreferredCategoryResponseDto.ktRepository: SPOTeam/Android-V2
Length of output: 551
StudyTheme.from() 매핑 방식을 SocialLoginType.from() 패턴과 일관성 있게 정정하세요.
현재 enum name 완전 일치 방식만 사용하는데, 같은 파일의 SocialLoginType은 소문자 정규화와 title 필드 매칭을 적용하고 있습니다. API가 소문자나 한글 title 값을 보내면 null이 되는 위험이 있으므로, 다음과 같이 case-insensitive와 title 필드 매칭을 추가하세요.
🔧 매핑 범위 확장 예시
companion object {
fun from(value: String): StudyTheme? =
values().firstOrNull { it.name == value }
} companion object {
fun from(value: String): StudyTheme? {
val normalized = value.trim()
return values().firstOrNull {
it.name.equals(normalized, ignoreCase = true) || it.title == normalized
}
}
}📝 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.
| OTHER("기타"); | |
| companion object { | |
| fun from(value: String): StudyTheme? = | |
| values().firstOrNull { it.name == value } | |
| } | |
| OTHER("기타"); | |
| companion object { | |
| fun from(value: String): StudyTheme? { | |
| val normalized = value.trim() | |
| return values().firstOrNull { | |
| it.name.equals(normalized, ignoreCase = true) || it.title == normalized | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt` around lines 63 -
68, StudyTheme.from currently only matches enum.name exactly; make it consistent
with SocialLoginType.from by normalizing input and matching both the enum name
and its title case-insensitively. Update the StudyTheme.from function to trim
and case-normalize the incoming value and return the first matching enum where
value equals name (ignore case) or equals title (ignore case) — keep the method
on the StudyTheme companion object and follow the same matching logic used in
SocialLoginType.from.
| companion object { | ||
| fun from(value: String): SocialLoginType { | ||
| val normalized = value.lowercase() | ||
|
|
||
| return values().firstOrNull { | ||
| it.title == normalized | ||
| } ?: KAKAO | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "Global.kt" --type fRepository: SPOTeam/Android-V2
Length of output: 119
🏁 Script executed:
cat -n core/model/src/main/java/com/umcspot/spot/model/Global.kt | head -120Repository: SPOTeam/Android-V2
Length of output: 3038
🏁 Script executed:
rg "SocialLoginType\.from\(" --type kotlin --type java -B 2 -A 2Repository: SPOTeam/Android-V2
Length of output: 632
🏁 Script executed:
rg "SocialLoginType" --type kotlin --type java | head -30Repository: SPOTeam/Android-V2
Length of output: 3859
🏁 Script executed:
rg "\.from\(" core/model/src/main/java/com/umcspot/spot/model/Global.kt -A 10 -B 2Repository: SPOTeam/Android-V2
Length of output: 44
🏁 Script executed:
cat -n data/user/src/main/java/com/umcspot/spot/user/mapper/UserMapper.kt | head -50Repository: SPOTeam/Android-V2
Length of output: 2350
알 수 없는 로그인 타입을 KAKAO로 기본 처리하면 오분류됩니다.
신규/오타 값이 들어오면 잘못된 타입으로 저장될 수 있습니다. UserMapper.kt에서 API 응답의 loginType을 매핑할 때 예상치 못한 값이 오면 KAKAO로 처리되어 데이터 불일치가 발생합니다. UNKNOWN 추가 또는 nullable 반환을 고려해 주세요. 또한 lowercase()는 Locale 영향을 받으니 Locale.ROOT 권장입니다.
🔧 안전한 기본값/로케일 처리 예시
+import java.util.Locale
enum class SocialLoginType(
val title: String
) {
+ UNKNOWN("unknown"),
KAKAO("kakao"),
NAVER("naver");
companion object {
fun from(value: String): SocialLoginType {
- val normalized = value.lowercase()
+ val normalized = value.lowercase(Locale.ROOT)
return values().firstOrNull {
it.title == normalized
- } ?: KAKAO
+ } ?: UNKNOWN
}
}
}🤖 Prompt for AI Agents
In `@core/model/src/main/java/com/umcspot/spot/model/Global.kt` around lines 98 -
105, The from(value: String) factory in Global.kt currently lowercases the input
with value.lowercase() and defaults unknown values to KAKAO, causing
misclassification; change it to use value.lowercase(Locale.ROOT) and stop
defaulting to KAKAO—either return a nullable SocialLoginType? or add an UNKNOWN
enum constant and return that; then update callers such as UserMapper.kt (the
loginType mapping) to handle the nullable/UNKNOWN case explicitly instead of
silently treating it as KAKAO.
| mypageGraph( | ||
| contentPadding = contentPadding, | ||
| onParticipatingClick = { /*navigator.navigateToRecruitingStudy()*/ }, | ||
| onMyRecruitingClick = { /*navigator.navigateToRecruitingStudy()*/ }, | ||
| onMyAppliedClick = { /*navigator.navigateToRecruitingStudy()*/ }, | ||
| onEditInterestClick = { /*navigator.navigateToCheckList*/ }, | ||
| onEditInterestLocationClick = { } | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "MainNavHost.kt" | head -20Repository: SPOTeam/Android-V2
Length of output: 127
🏁 Script executed:
wc -l feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.ktRepository: SPOTeam/Android-V2
Length of output: 129
🏁 Script executed:
sed -n '100,120p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.ktRepository: SPOTeam/Android-V2
Length of output: 942
🏁 Script executed:
sed -n '1,130p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.kt | tail -50Repository: SPOTeam/Android-V2
Length of output: 2028
🏁 Script executed:
grep -n -i "TODO\|FIXME\|WIP" feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.ktRepository: SPOTeam/Android-V2
Length of output: 124
🏁 Script executed:
sed -n '100,120p' feature/main/src/main/java/com/umcspot/spot/main/MainNavHost.ktRepository: SPOTeam/Android-V2
Length of output: 942
마이페이지 그래프의 클릭 콜백이 모두 비활성 상태입니다.
mypageGraph의 5개 콜백(onParticipatingClick, onMyRecruitingClick, onMyAppliedClick, onEditInterestClick, onEditInterestLocationClick)이 모두 주석 처리되거나 빈 람다식입니다. 사용자 클릭 시 네비게이션이 작동하지 않으므로 각 콜백에 의도된 동작을 연결해주세요.
| InterestedInfo( | ||
| title = "관심 분야", | ||
| icon = painterResource(id = R.drawable.search_prefer), | ||
| interest = data, | ||
| onClick = {} | ||
| ) | ||
|
|
||
| Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) | ||
|
|
||
| HorizontalDivider( | ||
| color = SpotTheme.colors.G300, | ||
| thickness = 1.dp, | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = screenWidthDp(4.dp)) | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| item { | ||
| when(preferRegion) { | ||
| is UiState.Loading, is UiState.Empty, is UiState.Failure -> { | ||
| Box( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .wrapContentHeight(), | ||
| contentAlignment = Alignment.Center | ||
| ) { | ||
| SpotSpinner() | ||
| } | ||
|
|
||
| Spacer(modifier = Modifier.height(screenHeightDp(20.dp))) | ||
| } | ||
|
|
||
| is UiState.Success -> { | ||
| val data = preferRegion.data | ||
|
|
||
| Spacer(modifier = Modifier.height(screenHeightDp(7.dp))) | ||
|
|
||
| InterestedInfo( | ||
| title = "관심 지역", | ||
| icon = painterResource(id = R.drawable.location_outline), | ||
| interest = data, | ||
| onClick = {} | ||
| ) |
There was a problem hiding this comment.
관심 편집 클릭 핸들러가 연결되지 않음.
상위에서 전달받은 onEditInterestClick/onEditInterestLocationClick이 실제 버튼에 연결되지 않아 클릭이 동작하지 않습니다.
🛠️ 제안 수정
- onClick = {}
+ onClick = onEditInterestClick
...
- onClick = {}
+ onClick = onEditInterestLocationClick🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`
around lines 186 - 231, The InterestedInfo composables are using empty lambdas
so the passed-in handlers aren't wired; replace the onClick = {} for the "관심 분야"
InterestedInfo with the provided onEditInterestClick and the onClick = {} for
the "관심 지역" InterestedInfo with onEditInterestLocationClick (using the same
parameter names from the parent) so the click handlers are invoked; ensure you
reference the InterestedInfo usage lines and preserve the existing data passed
as interest.
| fun InterestedInfo( | ||
| title : String, | ||
| icon : Painter, | ||
| interest : List<String?>, | ||
| onClick : () -> Unit | ||
| ) { | ||
| val interestText = remember(interest) { | ||
| interest.joinToString(separator = " ") { "#$it" } | ||
| } |
There was a problem hiding this comment.
관심 목록에 null이 있으면 null 문자열이 노출됩니다.
nullable 리스트를 그대로 join 하면 #null이 표시될 수 있습니다.
🛠️ 제안 수정
- val interestText = remember(interest) {
- interest.joinToString(separator = " ") { "#$it" }
- }
+ val interestText = remember(interest) {
+ interest
+ .filterNotNull()
+ .filter { it.isNotBlank() }
+ .joinToString(separator = " ") { "#$it" }
+ }📝 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 InterestedInfo( | |
| title : String, | |
| icon : Painter, | |
| interest : List<String?>, | |
| onClick : () -> Unit | |
| ) { | |
| val interestText = remember(interest) { | |
| interest.joinToString(separator = " ") { "#$it" } | |
| } | |
| fun InterestedInfo( | |
| title : String, | |
| icon : Painter, | |
| interest : List<String?>, | |
| onClick : () -> Unit | |
| ) { | |
| val interestText = remember(interest) { | |
| interest | |
| .filterNotNull() | |
| .filter { it.isNotBlank() } | |
| .joinToString(separator = " ") { "#$it" } | |
| } |
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt`
around lines 491 - 499, In InterestedInfo, the nullable entries in interest can
produce "#null" when joined; update the logic that builds interestText to filter
out nulls (and optionally blank strings) before joining so only non-null values
are prefixed with "#" — for example, use interest.filterNotNull().filter {
it.isNotBlank() }.joinToString(" ") { "#$it" } inside the remember block so
InterestedInfo produces no "#null" tokens.
feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt
Show resolved
Hide resolved
| fun loadMemberInfo() { | ||
| _uiState.update { it.copy(memberInfo = UiState.Loading) } | ||
|
|
||
| viewModelScope.launch { | ||
| userRepository.getMyPageInfo() | ||
| .onSuccess { info -> | ||
| _uiState.update { it.copy(memberInfo = UiState.Success(info)) } | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("HomeViewModel", "loadMyPageInfo error", e) | ||
| // _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) } | ||
| } |
There was a problem hiding this comment.
실패 시 memberInfo가 Loading에 고착됩니다.
onFailure에서 Failure로 갱신하지 않아 UI가 계속 로딩 상태로 남을 수 있습니다.
✅ 수정 제안
.onFailure { e ->
Log.e("HomeViewModel", "loadMyPageInfo error", e)
-// _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) }
+ _uiState.update {
+ it.copy(memberInfo = UiState.Failure(e.message ?: "내 정보 불러오기 실패"))
+ }
}🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 28 - 39, The onFailure branch in loadMemberInfo currently only logs
the error and leaves _uiState.memberInfo as UiState.Loading; update the
onFailure handler to set _uiState.update { it.copy(memberInfo =
UiState.Failure(e.message ?: "회원 정보 불러오기 실패")) } (or a localized message) so
memberInfo moves to Failure; modify the onFailure lambda where
userRepository.getMyPageInfo() is called to perform this _uiState update and
keep the Log.e call.
| fun loadPrefer() { | ||
| _uiState.update { | ||
| it.copy( | ||
| preferRegions = UiState.Loading, | ||
| preferCategories = UiState.Loading | ||
| ) | ||
| } | ||
|
|
||
| viewModelScope.launch { | ||
| userRepository.getUserPreferredRegionName() | ||
| .onSuccess { info -> | ||
| _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) } | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("HomeViewModel", "loadPreferredRegion error", e) | ||
| // _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) } | ||
| } | ||
| } | ||
|
|
||
| viewModelScope.launch { | ||
| userRepository.getUserPreferredCategory() | ||
| .onSuccess { info -> | ||
| _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) } | ||
| } | ||
| .onFailure { e -> | ||
| Log.e("HomeViewModel", "loadPreferCategories error", e) | ||
| // _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) } | ||
| } | ||
| } |
There was a problem hiding this comment.
선호 지역/카테고리도 실패 시 Loading에 고착됩니다.
각 onFailure에서 Failure 상태로 전환하지 않으면 사용자에게 무한 로딩으로 보입니다.
✅ 수정 제안
userRepository.getUserPreferredRegionName()
.onSuccess { info ->
_uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferredRegion error", e)
-// _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) }
+ _uiState.update {
+ it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패"))
+ }
}
@@
userRepository.getUserPreferredCategory()
.onSuccess { info ->
_uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) }
}
.onFailure { e ->
Log.e("HomeViewModel", "loadPreferCategories error", e)
-// _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) }
+ _uiState.update {
+ it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패"))
+ }
}📝 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 loadPrefer() { | |
| _uiState.update { | |
| it.copy( | |
| preferRegions = UiState.Loading, | |
| preferCategories = UiState.Loading | |
| ) | |
| } | |
| viewModelScope.launch { | |
| userRepository.getUserPreferredRegionName() | |
| .onSuccess { info -> | |
| _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) } | |
| } | |
| .onFailure { e -> | |
| Log.e("HomeViewModel", "loadPreferredRegion error", e) | |
| // _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) } | |
| } | |
| } | |
| viewModelScope.launch { | |
| userRepository.getUserPreferredCategory() | |
| .onSuccess { info -> | |
| _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) } | |
| } | |
| .onFailure { e -> | |
| Log.e("HomeViewModel", "loadPreferCategories error", e) | |
| // _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) } | |
| } | |
| } | |
| fun loadPrefer() { | |
| _uiState.update { | |
| it.copy( | |
| preferRegions = UiState.Loading, | |
| preferCategories = UiState.Loading | |
| ) | |
| } | |
| viewModelScope.launch { | |
| userRepository.getUserPreferredRegionName() | |
| .onSuccess { info -> | |
| _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) } | |
| } | |
| .onFailure { e -> | |
| Log.e("HomeViewModel", "loadPreferredRegion error", e) | |
| _uiState.update { | |
| it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) | |
| } | |
| } | |
| } | |
| viewModelScope.launch { | |
| userRepository.getUserPreferredCategory() | |
| .onSuccess { info -> | |
| _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) } | |
| } | |
| .onFailure { e -> | |
| Log.e("HomeViewModel", "loadPreferCategories error", e) | |
| _uiState.update { | |
| it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 43 - 71, In loadPrefer(), after calling
userRepository.getUserPreferredRegionName() and getUserPreferredCategory()
inside the viewModelScope.launch blocks, set the corresponding _uiState fields
(preferRegions and preferCategories) to UiState.Failure with a useful message in
each onFailure handler instead of only logging; update the onFailure for
getUserPreferredRegionName() to call _uiState.update { it.copy(preferRegions =
UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) } and similarly update the
onFailure for getUserPreferredCategory() to call _uiState.update {
it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) } so
the UI leaves the Loading state on errors.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`:
- Around line 101-104: The catch block for "catch (e: Exception)" swallows the
exception making debugging difficult; update the handler to log the exception
(including stacktrace and message) using the project's logger (e.g., Timber/Log)
and include the error message in the UiState.Failure payload so the UI/state
contains the failure reason—specifically modify the code around _uiState.update
and UiState.Failure to pass e.message (or e.localizedMessage) and call the
logger with the exception.
♻️ Duplicate comments (4)
feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt (2)
32-43: memberInfo 실패 시 Loading에 고착됩니다.onFailure에서 Failure로 전환하지 않으면 로딩 스피너가 계속 노출될 수 있습니다.
✅ 수정 제안
.onFailure { e -> Log.e("HomeViewModel", "loadMyPageInfo error", e) -// _uiState.update { it.copy(weatherInfo = UiState.Failure(e.message ?: "날씨 불러오기 실패")) } + _uiState.update { + it.copy(memberInfo = UiState.Failure(e.message ?: "내 정보 불러오기 실패")) + } }
55-74: 선호 정보도 실패 시 Failure 전환이 필요합니다.현재는 로그만 남아 UI가 로딩에 머물 수 있습니다.
✅ 수정 제안
userRepository.getUserPreferredRegionName() .onSuccess { info -> _uiState.update { it.copy(preferRegions = UiState.Success(info.regionCodes)) } } .onFailure { e -> Log.e("HomeViewModel", "loadPreferredRegion error", e) -// _uiState.update { it.copy(popularPostInfo = UiState.Failure(e.message ?: "인기 정보 실패")) } + _uiState.update { + it.copy(preferRegions = UiState.Failure(e.message ?: "선호 지역 불러오기 실패")) + } } @@ userRepository.getUserPreferredCategory() .onSuccess { info -> _uiState.update { it.copy(preferCategories = UiState.Success(info.categories)) } } .onFailure { e -> Log.e("HomeViewModel", "loadPreferCategories error", e) -// _uiState.update { it.copy(recommendStudies = UiState.Failure(e.message ?: "추천 스터디 실패")) } + _uiState.update { + it.copy(preferCategories = UiState.Failure(e.message ?: "선호 카테고리 불러오기 실패")) + } }feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageScreen.kt (2)
189-234: 관심 편집 클릭이 빈 람다로 막혀 있습니다.전달받은 핸들러가 연결되지 않아 클릭이 동작하지 않습니다.
✅ 수정 제안
InterestedInfo( title = "관심 분야", icon = painterResource(id = R.drawable.search_prefer), interest = data, - onClick = {} + onClick = onEditInterestClick ) @@ InterestedInfo( title = "관심 지역", icon = painterResource(id = R.drawable.location_outline), interest = data, - onClick = {} + onClick = onEditInterestLocationClick )
519-520: 관심 목록에 null이 있으면 ‘#null’이 노출됩니다.UI 노이즈 방지를 위해 null/blank 필터링을 권장합니다.
✅ 수정 제안
val interestText = remember(interest) { - interest.joinToString(separator = " ") { "#$it" } + interest + .filterNotNull() + .filter { it.isNotBlank() } + .joinToString(separator = " ") { "#$it" } }
There was a problem hiding this comment.
예외 로그 없이 삼켜 디버깅이 어렵습니다.
실패 원인을 추적할 수 있도록 로그와 메시지 포함을 권장합니다.
✅ 수정 제안
} catch (e: Exception) {
+ Log.e("MyPageViewModel", "loadAppVersion error", e)
_uiState.update {
- it.copy(appVersion = UiState.Failure("앱 버전 조회 실패"))
+ it.copy(appVersion = UiState.Failure(e.message ?: "앱 버전 조회 실패"))
}
}🧰 Tools
🪛 detekt (1.23.8)
[warning] 101-101: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🤖 Prompt for AI Agents
In `@feature/mypage/src/main/java/com/umcspot/spot/mypage/main/MyPageViewModel.kt`
around lines 101 - 104, The catch block for "catch (e: Exception)" swallows the
exception making debugging difficult; update the handler to log the exception
(including stacktrace and message) using the project's logger (e.g., Timber/Log)
and include the error message in the UiState.Failure payload so the UI/state
contains the failure reason—specifically modify the code around _uiState.update
and UiState.Failure to pass e.message (or e.localizedMessage) and call the
logger with the exception.
Related issue 🛠
Work Description 📝
Screenshot 📸
Uncompleted Tasks 😅
PR Point 📌
트러블 슈팅 💥
Summary by CodeRabbit
새로운 기능
UI/UX 개선
기타
✏️ Tip: You can customize this high-level summary in your review settings.