-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 마이페이지 ui 수정 #777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 마이페이지 ui 수정 #777
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Walkthrough마이페이지에 MyLibrary 기능을 통합하고 전체 UI를 개편합니다. MyPageFragment에 ViewModel 바인딩, 장르 선호도 어댑터, 이벤트 핸들러를 추가하고, 레이아웃을 재구성하여 라이브러리 통계, 선호 장르, 키워드 칩을 표시하는 기능을 구현합니다. Changes
Sequence DiagramsequenceDiagram
participant Fragment as MyPageFragment
participant ViewModel as MyLibraryViewModel
participant Observer as Observer(uiState)
participant UI as UI Components
Fragment->>ViewModel: viewModels() 초기화
Fragment->>Fragment: onViewCreated에서 binding 설정
Fragment->>ViewModel: uiState 옵저버 등록
ViewModel->>Observer: uiState 발행
Observer->>UI: handleLibraryLoadingState()<br/>(로딩/에러 상태 처리)
Observer->>UI: updateRestGenrePreferenceVisibility()<br/>(장르 리스트 토글)
Observer->>UI: applyTextColors()<br/>(매력 포인트 텍스트 스타일링)
Observer->>UI: updateNovelPreferencesKeywords()<br/>(키워드 칩 추가)
Observer->>UI: updateDominantGenres()<br/>(주요 장르 이미지 로드)
Observer->>UI: updateGenreBadgeTitle()<br/>(배지 카운트 색상 지정)
Fragment->>UI: onStorageButtonClick() 이벤트
UI->>Fragment: FragmentResult로 라이브러리 네비게이션
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In @app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.kt:
- Around line 236-270: The current logic uses combinedText.split(fixedText) and
then checks splitText.isNotEmpty(), but split always returns a non-empty list so
the else branch never runs; change the guard to check whether fixedText is
actually present (e.g., combinedText.contains(fixedText) or
combinedText.indexOf(fixedText) >= 0) before splitting, and only call split and
build the two SpannableStrings when fixedText exists; otherwise create a single
SpannableString from combinedText and append it to spannableStringBuilder
(referencing combinedText, fixedText, splitText, and spannableStringBuilder in
MyPageFragment).
- Around line 304-314: updateDominantGenres currently assumes topGenres has 3
items which can leave stale images or throw IndexOutOfBoundsException; update
the function to safely handle lists smaller than 3 by first clearing or setting
a placeholder on binding.ivMyLibraryDominantGenreFirstLogo, SecondLogo, and
ThirdLogo, then iterate topGenres with forEachIndexed to compute
updatedGenreImageUrl via binding.root.getS3ImageUrl and call load only for
present indices (use the same when(index) branches you have), leaving the absent
indexes explicitly cleared/placeholdered to avoid stale images and exceptions.
In @app/src/main/res/drawable/btn_profile_edit.xml:
- Around line 1-14: The drawable btn_profile_edit.xml is unused and should be
removed; if you intend to keep it, replace the hardcoded color literals
(#ffffff, #DDDDE3, #949399) with color resource references (e.g., @color/...)
and update any consumers to use the resource variants; locate
btn_profile_edit.xml (the <vector> with two <path> elements) and either delete
the file from the repo or change the android:fillColor and android:strokeColor
attributes to reference defined colors in colors.xml, then run a project-wide
search to confirm no references remain before committing.
In @app/src/main/res/layout/fragment_my_page.xml:
- Around line 333-346: The layout directly indexes
myLibraryViewModel.uiState.topGenres[0] (and similar indexes), which can throw
IndexOutOfBoundsException when the list has fewer than three items; fix by
exposing safe nullable accessors on the UI state (e.g., add
firstGenre/secondGenre/thirdGenre properties that use
topGenres.getOrNull(index)) or implement a binding adapter like safeGet to
return null/default when out of range, and update the TextView bindings to use
those safe properties (e.g., myLibraryViewModel.uiState.firstGenre?.genreName
and firstGenre?.genreCount) so the layout never directly indexes the list.
🧹 Nitpick comments (2)
app/src/main/res/layout/fragment_my_page.xml (2)
156-175: TextView에서 불필요한layout_weight속성
wrap_content너비를 가진 TextView에layout_weight="1"이 설정되어 있습니다. 부모 LinearLayout에 이미layout_weight가 적용되어 있으므로, 자식 TextView의layout_weight는 불필요하며 제거해도 됩니다.♻️ 제안하는 수정 (예시: 첫 번째 섹션)
<TextView android:id="@+id/tv_my_linear_storage_interesting_count" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" android:text='@{String.valueOf(myLibraryViewModel.uiState.novelStats.interestNovelCount)}' android:textAppearance="@style/title2" android:textColor="@color/black" tools:text="12" /> <TextView android:id="@+id/tv_my_linear_storage_interesting_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" - android:layout_weight="1" android:text="@string/my_library_interesting" android:textAppearance="@style/body5" android:textColor="@color/black" />
58-67: 접근성: 클릭 가능한 ImageView에contentDescription누락설정 아이콘 등 클릭 가능한 ImageView에
contentDescription이 없습니다. 스크린 리더 사용자를 위해 추가해 주세요.♻️ 제안하는 수정
<ImageView android:id="@+id/iv_my_page_sticky_go_to_setting" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="12dp" android:padding="10dp" android:src="@drawable/ic_settings" + android:contentDescription="@string/setting_title" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" />
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.ktapp/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/model/MyLibraryUiState.ktapp/src/main/res/drawable/btn_profile_edit.xmlapp/src/main/res/layout/fragment_my_page.xmlcore/resource/src/main/res/values/strings.xml
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.kt (1)
app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/MyLibraryFragment.kt (7)
setupRestGenrePreferenceAdapter(52-54)onStorageButtonClick(197-209)updateRestGenrePreferenceVisibility(114-119)updateNovelPreferencesKeywords(168-183)updateDominantGenres(211-221)applyTextColors(121-166)createKeywordChip(185-195)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (8)
app/src/main/java/com/into/websoso/ui/main/myPage/myLibrary/model/MyLibraryUiState.kt (1)
16-19: LGTM!
totalBadgeCount계산 로직이 명확하고 올바릅니다.sumOf를 사용한 관용적인 Kotlin 코드입니다.core/resource/src/main/res/values/strings.xml (1)
218-219: LGTM!새로운 문자열 리소스가 적절하게 추가되었습니다.
my_page_badge의%d포맷 지정자가 올바르게 사용되었습니다.app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.kt (4)
50-56: LGTM!ViewModel과 어댑터의 초기화가 적절합니다.
viewModels()를 사용한 fragment-scoped ViewModel과lazy초기화 패턴이 올바르게 적용되었습니다.
275-290: LGTM!키워드 중복 방지 로직이 올바르게 구현되었습니다.
mutableSetOf을 사용한 기존 키워드 추적이 효율적입니다.
316-337: LGTM!뱃지 타이틀의 숫자 부분에 색상을 적용하는 로직이 올바르게 구현되었습니다.
SpannableStringBuilder와ForegroundColorSpan사용이 적절합니다.
349-358: LGTM!
throttleFirst를 사용한 중복 클릭 방지와FragmentResult를 통한 네비게이션 패턴이 적절합니다.app/src/main/res/layout/fragment_my_page.xml (2)
12-15: LGTM!
myLibraryViewModel바인딩 변수가 올바르게 추가되었습니다.
73-82: LGTM!
NestedScrollView와ConstraintLayout을 사용한 레이아웃 구조가 적절합니다.fillViewport="true"와overScrollMode="never"설정이 올바르게 적용되었습니다.
| private fun setupObserver() { | ||
| myPageViewModel.uiState.observe(viewLifecycleOwner) { uiState -> | ||
| when { | ||
| uiState.loading -> binding.wllMyPage.setWebsosoLoadingVisibility(true) | ||
| uiState.error -> binding.wllMyPage.setErrorLayoutVisibility(true) | ||
| uiState.loading -> { | ||
| binding.wllMyPage.setWebsosoLoadingVisibility(true) | ||
| } | ||
|
|
||
| uiState.error -> { | ||
| binding.wllMyPage.setErrorLayoutVisibility(true) | ||
| } | ||
|
|
||
| !uiState.loading -> { | ||
| binding.wllMyPage.setErrorLayoutVisibility(false) | ||
| binding.wllMyPage.setWebsosoLoadingVisibility(false) | ||
| setUpMyProfileImage(uiState.myProfile?.avatarImage.orEmpty()) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| myLibraryViewModel.uiState.observe(viewLifecycleOwner) { uiState -> | ||
| handleLibraryLoadingState(uiState) | ||
|
|
||
| if (!uiState.isLoading && !uiState.isError) { | ||
| val hasNoGenre = myLibraryViewModel.hasNoPreferences() | ||
| val hasNoNovel = !myLibraryViewModel.hasNovelPreferences() | ||
|
|
||
| if (hasNoGenre) { | ||
| binding.clMyLibraryGenrePreference.isVisible = false | ||
| binding.clMyLibraryUnknownPreference.isVisible = true | ||
|
|
||
| binding.viewMyPageNovelPreferenceDivider.isVisible = false | ||
| binding.clMyLibraryNovelPreference.isVisible = false | ||
| binding.clMyLibraryUnknownNovelPreference.isVisible = false | ||
| } else { | ||
| binding.clMyLibraryGenrePreference.isVisible = true | ||
| binding.clMyLibraryUnknownPreference.isVisible = false | ||
|
|
||
| binding.viewMyPageNovelPreferenceDivider.isVisible = true | ||
|
|
||
| if (hasNoNovel) { | ||
| binding.clMyLibraryNovelPreference.isVisible = false | ||
| binding.clMyLibraryUnknownNovelPreference.isVisible = true | ||
| } else { | ||
| binding.clMyLibraryNovelPreference.isVisible = true | ||
| binding.clMyLibraryUnknownNovelPreference.isVisible = false | ||
| } | ||
| } | ||
|
|
||
| binding.clMyLibraryAttractivePoints.isVisible = | ||
| myLibraryViewModel.hasAttractivePoints() | ||
|
|
||
| restGenrePreferenceAdapter.updateRestGenrePreferenceData(uiState.restGenres) | ||
| updateRestGenrePreferenceVisibility(uiState.isGenreListVisible) | ||
| uiState.novelPreferences?.let { updateNovelPreferencesKeywords(it) } | ||
| updateDominantGenres(uiState.topGenres) | ||
| updateGenreBadgeTitle(uiState.totalBadgeCount) | ||
|
|
||
| applyTextColors( | ||
| uiState.translatedAttractivePoints.joinToString(", ") + | ||
| getString(my_library_attractive_point_fixed_text), | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
두 ViewModel의 로딩 상태 간 경쟁 조건 가능성
myPageViewModel과 myLibraryViewModel 모두 동일한 wllMyPage 로딩 레이아웃을 제어합니다. 두 옵저버가 독립적으로 로딩 상태를 설정하므로, 한 ViewModel이 로딩을 완료했지만 다른 ViewModel이 아직 로딩 중인 경우 UI 깜빡임이나 불일치가 발생할 수 있습니다.
두 ViewModel의 로딩 상태를 결합하여 처리하는 것을 고려해 보세요:
// 예시: 두 상태를 결합하여 로딩 표시
val isLoading = myPageUiState.loading || myLibraryUiState.isLoading
binding.wllMyPage.setWebsosoLoadingVisibility(isLoading)| val splitText = combinedText.split(fixedText) | ||
|
|
||
| if (splitText.isNotEmpty()) { | ||
| val attractivePoints = | ||
| SpannableString(splitText[0]).apply { | ||
| setSpan( | ||
| ForegroundColorSpan(primary100), | ||
| 0, | ||
| length, | ||
| SPAN_EXCLUSIVE_EXCLUSIVE, | ||
| ) | ||
| } | ||
| spannableStringBuilder.append(attractivePoints) | ||
|
|
||
| val fixedSpannable = | ||
| SpannableString(fixedText).apply { | ||
| setSpan( | ||
| ForegroundColorSpan(gray300), | ||
| 0, | ||
| length, | ||
| SPAN_EXCLUSIVE_EXCLUSIVE, | ||
| ) | ||
| } | ||
| spannableStringBuilder.append(fixedSpannable) | ||
| } else { | ||
| val spannable = SpannableString(combinedText).apply { | ||
| setSpan( | ||
| ForegroundColorSpan(primary100), | ||
| 0, | ||
| length, | ||
| SPAN_EXCLUSIVE_EXCLUSIVE, | ||
| ) | ||
| } | ||
| spannableStringBuilder.append(spannable) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
split 결과 확인 로직 개선 필요
combinedText.split(fixedText)는 fixedText가 없어도 빈 리스트를 반환하지 않습니다. splitText.isNotEmpty() 조건은 항상 true이므로 else 분기가 실행되지 않습니다.
fixedText가 실제로 포함되어 있는지 확인하려면:
♻️ 제안하는 수정
- if (splitText.isNotEmpty()) {
+ if (combinedText.contains(fixedText) && splitText.size > 1) {📝 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 splitText = combinedText.split(fixedText) | |
| if (splitText.isNotEmpty()) { | |
| val attractivePoints = | |
| SpannableString(splitText[0]).apply { | |
| setSpan( | |
| ForegroundColorSpan(primary100), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(attractivePoints) | |
| val fixedSpannable = | |
| SpannableString(fixedText).apply { | |
| setSpan( | |
| ForegroundColorSpan(gray300), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(fixedSpannable) | |
| } else { | |
| val spannable = SpannableString(combinedText).apply { | |
| setSpan( | |
| ForegroundColorSpan(primary100), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(spannable) | |
| } | |
| val splitText = combinedText.split(fixedText) | |
| if (combinedText.contains(fixedText) && splitText.size > 1) { | |
| val attractivePoints = | |
| SpannableString(splitText[0]).apply { | |
| setSpan( | |
| ForegroundColorSpan(primary100), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(attractivePoints) | |
| val fixedSpannable = | |
| SpannableString(fixedText).apply { | |
| setSpan( | |
| ForegroundColorSpan(gray300), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(fixedSpannable) | |
| } else { | |
| val spannable = SpannableString(combinedText).apply { | |
| setSpan( | |
| ForegroundColorSpan(primary100), | |
| 0, | |
| length, | |
| SPAN_EXCLUSIVE_EXCLUSIVE, | |
| ) | |
| } | |
| spannableStringBuilder.append(spannable) | |
| } |
🤖 Prompt for AI Agents
In @app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.kt around
lines 236 - 270, The current logic uses combinedText.split(fixedText) and then
checks splitText.isNotEmpty(), but split always returns a non-empty list so the
else branch never runs; change the guard to check whether fixedText is actually
present (e.g., combinedText.contains(fixedText) or
combinedText.indexOf(fixedText) >= 0) before splitting, and only call split and
build the two SpannableStrings when fixedText exists; otherwise create a single
SpannableString from combinedText and append it to spannableStringBuilder
(referencing combinedText, fixedText, splitText, and spannableStringBuilder in
MyPageFragment).
| private fun updateDominantGenres(topGenres: List<GenrePreferenceEntity>) { | ||
| topGenres.forEachIndexed { index, genrePreferenceEntity -> | ||
| val updatedGenreImageUrl = binding.root.getS3ImageUrl(genrePreferenceEntity.genreImage) | ||
|
|
||
| when (index) { | ||
| 0 -> binding.ivMyLibraryDominantGenreFirstLogo.load(updatedGenreImageUrl) | ||
| 1 -> binding.ivMyLibraryDominantGenreSecondLogo.load(updatedGenreImageUrl) | ||
| 2 -> binding.ivMyLibraryDominantGenreThirdLogo.load(updatedGenreImageUrl) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
topGenres가 3개 미만일 때 처리 필요
topGenres 리스트의 크기가 3 미만인 경우, 업데이트되지 않는 ImageView에 이전 이미지가 남아있을 수 있습니다. 또한 레이아웃에서 topGenres[0], topGenres[1], topGenres[2]를 직접 바인딩하고 있어 IndexOutOfBoundsException이 발생할 수 있습니다.
♻️ 제안하는 수정
private fun updateDominantGenres(topGenres: List<GenrePreferenceEntity>) {
+ // Clear previous images
+ binding.ivMyLibraryDominantGenreFirstLogo.setImageDrawable(null)
+ binding.ivMyLibraryDominantGenreSecondLogo.setImageDrawable(null)
+ binding.ivMyLibraryDominantGenreThirdLogo.setImageDrawable(null)
+
topGenres.forEachIndexed { index, genrePreferenceEntity ->
val updatedGenreImageUrl = binding.root.getS3ImageUrl(genrePreferenceEntity.genreImage)
when (index) {
0 -> binding.ivMyLibraryDominantGenreFirstLogo.load(updatedGenreImageUrl)
1 -> binding.ivMyLibraryDominantGenreSecondLogo.load(updatedGenreImageUrl)
2 -> binding.ivMyLibraryDominantGenreThirdLogo.load(updatedGenreImageUrl)
}
}
}🤖 Prompt for AI Agents
In @app/src/main/java/com/into/websoso/ui/main/myPage/MyPageFragment.kt around
lines 304 - 314, updateDominantGenres currently assumes topGenres has 3 items
which can leave stale images or throw IndexOutOfBoundsException; update the
function to safely handle lists smaller than 3 by first clearing or setting a
placeholder on binding.ivMyLibraryDominantGenreFirstLogo, SecondLogo, and
ThirdLogo, then iterate topGenres with forEachIndexed to compute
updatedGenreImageUrl via binding.root.getS3ImageUrl and call load only for
present indices (use the same when(index) branches you have), leaving the absent
indexes explicitly cleared/placeholdered to avoid stale images and exceptions.
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="27dp" | ||
| android:height="27dp" | ||
| android:viewportWidth="27" | ||
| android:viewportHeight="27"> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M13.481,0.5C20.608,0.5 26.5,6.378 26.5,13.494C26.5,20.621 20.621,26.5 13.494,26.5C6.379,26.5 0.5,20.621 0.5,13.494C0.5,6.38 6.366,0.5 13.481,0.5Z" | ||
| android:fillColor="#ffffff" | ||
| android:strokeColor="#DDDDE3"/> | ||
| <path | ||
| android:pathData="M18.208,11.335L19.62,9.925C20.319,9.221 20.345,8.451 19.711,7.799L19.205,7.277C18.571,6.637 17.781,6.703 17.095,7.394L15.683,8.79L18.208,11.335ZM10.644,18.926L17.485,12.032L14.973,9.514L8.131,16.381L6.862,19.461C6.72,19.852 7.121,20.283 7.51,20.139L10.644,18.926Z" | ||
| android:fillColor="#949399"/> | ||
| </vector> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify if btn_profile_edit drawable is used anywhere in the codebase
rg -n "btn_profile_edit" --type=xml --type=kotlinRepository: Team-WSS/WSS-Android
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Broader search for btn_profile_edit with various patterns
echo "=== Searching all file types for btn_profile_edit ==="
rg -n "btn_profile_edit" --max-count=20
echo -e "\n=== Checking if drawable file exists ==="
find . -name "btn_profile_edit.xml" -type f
echo -e "\n=== Searching for profile_edit pattern ==="
rg -n "profile_edit" --type=xml --type=kotlin | head -30
echo -e "\n=== Checking drawable references in my_page files ==="
rg -n "ic_my_page_edit_profile\|edit.*profile" --type=xml --type=kotlin | head -30Repository: Team-WSS/WSS-Android
Length of output: 3799
미사용 drawable 삭제 필요
이 drawable 파일은 코드베이스의 어떤 곳에서도 참조되지 않습니다. 사용되지 않는 파일이므로 삭제를 권장합니다.
또한 색상값(#ffffff, #DDDDE3, #949399)이 하드코딩되어 있습니다. 유지보수성을 위해 향후에는 색상값을 리소스로 정의하여 @color 참조를 사용해주세요.
🤖 Prompt for AI Agents
In @app/src/main/res/drawable/btn_profile_edit.xml around lines 1 - 14, The
drawable btn_profile_edit.xml is unused and should be removed; if you intend to
keep it, replace the hardcoded color literals (#ffffff, #DDDDE3, #949399) with
color resource references (e.g., @color/...) and update any consumers to use the
resource variants; locate btn_profile_edit.xml (the <vector> with two <path>
elements) and either delete the file from the repo or change the
android:fillColor and android:strokeColor attributes to reference defined colors
in colors.xml, then run a project-wide search to confirm no references remain
before committing.
| android:text="@{myLibraryViewModel.uiState.topGenres[0].genreName}" | ||
| android:textAppearance="@style/title3" | ||
| android:textColor="@color/black" | ||
| tools:text="로판" /> | ||
|
|
||
| <TextView | ||
| android:id="@+id/tv_my_library_dominant_genre_first_count" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_marginTop="2dp" | ||
| android:text='@{String.format(@string/my_library_genre_count, myLibraryViewModel.uiState.topGenres[0].genreCount)}' | ||
| android:textAppearance="@style/body5" | ||
| android:textColor="@color/gray_200_949399" | ||
| tools:text="12편" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
topGenres 배열 직접 접근 시 IndexOutOfBoundsException 발생 가능
데이터 바인딩에서 topGenres[0], topGenres[1], topGenres[2]에 직접 접근하고 있습니다. topGenres 리스트의 크기가 3 미만인 경우 런타임에 IndexOutOfBoundsException이 발생할 수 있습니다.
안전한 접근을 위해 safeGet 바인딩 어댑터를 사용하거나, ViewModel에서 개별 장르 속성을 제공하는 것을 권장합니다:
♻️ ViewModel에서 안전한 속성 제공 예시
// MyLibraryUiState 또는 MyLibraryViewModel에서
val firstGenre: GenrePreferenceEntity? get() = topGenres.getOrNull(0)
val secondGenre: GenrePreferenceEntity? get() = topGenres.getOrNull(1)
val thirdGenre: GenrePreferenceEntity? get() = topGenres.getOrNull(2)그런 다음 레이아웃에서:
android:text="@{myLibraryViewModel.uiState.firstGenre.genreName}"🤖 Prompt for AI Agents
In @app/src/main/res/layout/fragment_my_page.xml around lines 333 - 346, The
layout directly indexes myLibraryViewModel.uiState.topGenres[0] (and similar
indexes), which can throw IndexOutOfBoundsException when the list has fewer than
three items; fix by exposing safe nullable accessors on the UI state (e.g., add
firstGenre/secondGenre/thirdGenre properties that use
topGenres.getOrNull(index)) or implement a binding adapter like safeGet to
return null/default when out of range, and update the TextView bindings to use
those safe properties (e.g., myLibraryViewModel.uiState.firstGenre?.genreName
and firstGenre?.genreCount) so the layout never directly indexes the list.
s9hn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생히셨씁니다!
📌𝘐𝘴𝘴𝘶𝘦𝘴
📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯
📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵
💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴
Summary by CodeRabbit
릴리스 노트
New Features
Style
✏️ Tip: You can customize this high-level summary in your review settings.