[Refactor]: Home model#142
Conversation
📝 WalkthroughWalkthrough도메인/DTO 및 UI 모델을 컬렉션·북마크용 래퍼 리스트 모델로 전환하고, 소셜 인증 응답에 userName을 추가해 DataStore에 저장하도록 PreferencesManager 연동을 도입했습니다. 관련 UI·리포지토리·매퍼·API 어노테이션 및 패키지 네임스페이스 일부도 조정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Client(UI)
participant Repo as AuthRepository
participant API as AuthApi
participant Pref as PreferencesManager
rect rgba(200,200,255,0.5)
UI->>Repo: socialVerify(request)
Repo->>API: POST /api/v1/auth/social/verify
API-->>Repo: SocialVerifyResponseDto(userId, userName, tempToken)
Repo->>Pref: saveString(USER_NAME, userName)
Repo-->>UI: Result(SocialVerifyResponseModel)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/src/main/java/com/flint/presentation/login/LoginViewModel.kt (1)
29-34: userName 저장을 네비게이션 이전에 수행 권장현재 순서에서는 홈 화면 진입 직후 이름이 잠깐 비어 보일 수 있습니다. 저장을 먼저 수행하고 네비게이션을 emit 하는 편이 안전합니다.
✅ 순서 조정 제안
if (data.isRegistered) { - _navigationEvent.emit(UiState.Success(LoginNavigationEvent.NavigateToHome)) - preferencesManager.saveString(USER_NAME, data.userName.toString()) + preferencesManager.saveString(USER_NAME, data.userName.toString()) + _navigationEvent.emit(UiState.Success(LoginNavigationEvent.NavigateToHome)) } else {app/src/main/java/com/flint/data/dto/content/response/BookmarkedContentListResponseDto.kt (1)
13-23:imageUrl필드에 대한 역호환성 처리 필수
imageUrl필드가 non-null이면서 기본값이 없으면, 서버에서 이 필드를 내려주지 않는 경우 역직렬화가 실패합니다. 현재 설정된ignoreUnknownKeys와coerceInputValues는 누락된 필드를 처리하지 못합니다.nullable로 처리하거나 기본값을 제공하여 안전하게 방어하세요.
✅ 안전한 처리 방안
`@SerialName`("imageUrl") - val imageUrl: String, + val imageUrl: String? = null,app/src/main/java/com/flint/presentation/profile/ProfileUiState.kt (1)
22-36:Empty상태에서FakeList사용은 의미적으로 부적절함
Empty상태는 초기 또는 빈 상태를 나타내야 하는데,FakeList를 사용하면 실제로는 테스트 데이터가 포함됩니다. 이로 인해 초기 UI에 가짜 데이터가 표시되거나 디버깅 시 혼란이 발생할 수 있습니다.🔧 수정 제안
빈 모델을 위한
Empty속성이CollectionListModel과BookmarkedContentListModel에 정의되어 있다면 사용하거나, 없다면 추가를 고려해 주세요:val Empty = ProfileUiState( keywords = persistentListOf(), profile = AuthorModel( userId = "0", nickname = "", profileUrl = "", userRole = UserRoleType.FLINER, ), - createCollections = CollectionListModel.FakeList, - savedCollections = CollectionListModel.FakeList, - savedContent = BookmarkedContentListModel.FakeList, + createCollections = CollectionListModel(), // 또는 CollectionListModel.Empty + savedCollections = CollectionListModel(), + savedContent = BookmarkedContentListModel(), )
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt`:
- Around line 9-10: 파일 상단에 선언된 import 중 사용되지 않는 CollectionModel 및 AuthorModel
import를 제거하세요; 현재 이 파일은 CollectionItemModel과 CollectionListModel만 사용하므로 불필요한
import 문(CollectionModel, AuthorModel)을 삭제해 정리하고 IDE/빌드 경고가 없도록 하세요.
In `@app/src/main/java/com/flint/presentation/home/HomeScreen.kt`:
- Around line 54-75: HomeScreen is called from HomeRoute without passing the new
userName from HomeUiState, so HomeScreen always shows an empty name; update the
HomeRoute (where HomeScreen(...) is invoked) to pass uiState.userName (or the
appropriate HomeUiState.userName property) into the HomeScreen userName
parameter so the screen receives and displays the actual user name; reference
symbols: HomeRoute, HomeUiState.userName, and HomeScreen(userName = ...).
🧹 Nitpick comments (5)
app/src/main/java/com/flint/core/designsystem/component/image/NetworkImage.kt (1)
4-4: 사용되지 않는 import를 제거하세요.
androidx.compose.foundation.backgroundimport가 추가되었지만 파일 내에서 실제로 사용되지 않습니다. 코드 정리를 위해 제거하는 것을 권장합니다.♻️ 제안된 수정사항
-import androidx.compose.foundation.backgroundapp/src/main/java/com/flint/domain/model/collection/CollectionListModel.kt (1)
30-41: CollectionItemModel 기본값 사용에 대한 고려사항.모든 프로퍼티에 기본값이 설정되어 있어 편리하지만, 필수 필드(예:
id,title)에 기본값이 있으면 매핑 시 누락된 값을 감지하기 어려울 수 있습니다. 필수 필드는 기본값 없이 명시적으로 전달받는 것을 고려해 보세요.💡 필수 필드에서 기본값 제거 예시
data class CollectionItemModel( - val id: String = "", - val thumbnailUrl: String = "", - val title: String = "", + val id: String, + val thumbnailUrl: String, + val title: String, val description: String = "", val imageList: List<String> = listOf(), val bookmarkCount: Int = 0, val isBookmarked: Boolean = false, - val userId: String = "", - val nickname: String = "", + val userId: String, + val nickname: String, val profileUrl: String? = null )app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt (1)
19-53: 중복 코드 통합 고려.
RecommendCollectionItemResponseDto.toModel()과RecentCollectionItemResponseDto.toModel()의 구현이 동일합니다. 두 DTO가 동일한 필드 구조를 가진다면 공통 인터페이스나 제네릭 매핑 함수로 중복을 제거할 수 있습니다.♻️ 공통 인터페이스를 활용한 중복 제거 예시
// 공통 인터페이스 정의 (DTO 모듈에서) interface CollectionItemDto { val id: String val thumbnailUrl: String val title: String val description: String val imageList: List<String> val bookmarkCount: Int val isBookmarked: Boolean val userId: String val nickname: String val profileUrl: String? } // 단일 매퍼 함수 private fun CollectionItemDto.toModel(): CollectionItemModel = CollectionItemModel( id = id, thumbnailUrl = thumbnailUrl, title = title, description = description, imageList = imageList, bookmarkCount = bookmarkCount, isBookmarked = isBookmarked, userId = userId, nickname = nickname, profileUrl = profileUrl )app/src/main/java/com/flint/domain/repository/HomeRepository.kt (1)
6-7: 사용하지 않는 import 확인 필요.
CollectionRepository.kt와 마찬가지로CollectionModelimport가 사용되지 않는 것으로 보입니다.app/src/main/java/com/flint/domain/repository/CollectionRepository.kt (1)
6-7: 사용되지 않는 import 제거 필요
CollectionModelimport(Line 7)는 파일에서 사용되지 않습니다. 반환 타입이CollectionListModel로 변경되었으므로 제거하세요.
| import com.flint.domain.model.collection.CollectionModel | ||
| import com.flint.domain.model.user.AuthorModel |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the structure of the file
cd app/src/main/java/com/flint/domain/mapper/collection/ && wc -l CollectionMapper.ktRepository: imflint/Flint-Android
Length of output: 87
🏁 Script executed:
# Read the full file to see all content
cat -n app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.ktRepository: imflint/Flint-Android
Length of output: 2278
🏁 Script executed:
# Search for actual usage of CollectionModel and AuthorModel (excluding import lines)
rg -n "CollectionModel|AuthorModel" app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt | grep -v "^[0-9]*:\s*import"Repository: imflint/Flint-Android
Length of output: 47
사용하지 않는 import 제거 필요
CollectionModel(9번 줄)과 AuthorModel(10번 줄) import가 파일 내에서 사용되지 않으므로 제거하세요. 현재 코드는 CollectionItemModel과 CollectionListModel만 사용 중입니다.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/domain/mapper/collection/CollectionMapper.kt`
around lines 9 - 10, 파일 상단에 선언된 import 중 사용되지 않는 CollectionModel 및 AuthorModel
import를 제거하세요; 현재 이 파일은 CollectionItemModel과 CollectionListModel만 사용하므로 불필요한
import 문(CollectionModel, AuthorModel)을 삭제해 정리하고 IDE/빌드 경고가 없도록 하세요.
| HomeScreen( | ||
| recommendCollectionModelList = recommendedCollectionList, | ||
| recentCollectionModelList = recentCollectionList, | ||
| savedContentModelList = bookmarkedContentList, | ||
| navigateToCollectionCreate = { | ||
| navigateToCollectionCreate() | ||
| }, | ||
| navigateToExplore = { | ||
| // TODO navigate to explore | ||
| }, | ||
| onRecentCollectionItemClick = { collectionId -> | ||
| navigateToCollectionDetail(collectionId) | ||
| }, | ||
| onRecentCollectionAllClick = navigateToCollectionList, | ||
| onRecommendCollectionItemClick = { collectionId -> | ||
| navigateToCollectionDetail(collectionId) | ||
| }, | ||
| onSavedContentItemClick = { contentId -> | ||
| // TODO show OttListBottomSheet | ||
| }, | ||
| modifier = Modifier.padding(paddingValues), | ||
| ) |
There was a problem hiding this comment.
userName 파라미터가 전달되지 않음
HomeUiState에 userName 속성이 추가되었지만, HomeRoute에서 HomeScreen을 호출할 때 해당 값을 전달하지 않고 있습니다. 이로 인해 사용자 이름이 항상 빈 문자열로 표시됩니다.
🔧 수정 제안
HomeScreen(
+ userName = uiState.userName,
recommendCollectionModelList = recommendedCollectionList,
recentCollectionModelList = recentCollectionList,
savedContentModelList = bookmarkedContentList,📝 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.
| HomeScreen( | |
| recommendCollectionModelList = recommendedCollectionList, | |
| recentCollectionModelList = recentCollectionList, | |
| savedContentModelList = bookmarkedContentList, | |
| navigateToCollectionCreate = { | |
| navigateToCollectionCreate() | |
| }, | |
| navigateToExplore = { | |
| // TODO navigate to explore | |
| }, | |
| onRecentCollectionItemClick = { collectionId -> | |
| navigateToCollectionDetail(collectionId) | |
| }, | |
| onRecentCollectionAllClick = navigateToCollectionList, | |
| onRecommendCollectionItemClick = { collectionId -> | |
| navigateToCollectionDetail(collectionId) | |
| }, | |
| onSavedContentItemClick = { contentId -> | |
| // TODO show OttListBottomSheet | |
| }, | |
| modifier = Modifier.padding(paddingValues), | |
| ) | |
| HomeScreen( | |
| userName = uiState.userName, | |
| recommendCollectionModelList = recommendedCollectionList, | |
| recentCollectionModelList = recentCollectionList, | |
| savedContentModelList = bookmarkedContentList, | |
| navigateToCollectionCreate = { | |
| navigateToCollectionCreate() | |
| }, | |
| navigateToExplore = { | |
| // TODO navigate to explore | |
| }, | |
| onRecentCollectionItemClick = { collectionId -> | |
| navigateToCollectionDetail(collectionId) | |
| }, | |
| onRecentCollectionAllClick = navigateToCollectionList, | |
| onRecommendCollectionItemClick = { collectionId -> | |
| navigateToCollectionDetail(collectionId) | |
| }, | |
| onSavedContentItemClick = { contentId -> | |
| // TODO show OttListBottomSheet | |
| }, | |
| modifier = Modifier.padding(paddingValues), | |
| ) |
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/presentation/home/HomeScreen.kt` around lines 54
- 75, HomeScreen is called from HomeRoute without passing the new userName from
HomeUiState, so HomeScreen always shows an empty name; update the HomeRoute
(where HomeScreen(...) is invoked) to pass uiState.userName (or the appropriate
HomeUiState.userName property) into the HomeScreen userName parameter so the
screen receives and displays the actual user name; reference symbols: HomeRoute,
HomeUiState.userName, and HomeScreen(userName = ...).
nahy-512
left a comment
There was a problem hiding this comment.
고생하셨습니다~! 길 잘 닦아주셔서 감사합니다
| val FakeList = CollectionListModel( | ||
| collections = persistentListOf( | ||
| CollectionItemModel( | ||
| id = "0", | ||
| thumbnailUrl = "", | ||
| title = "드라마 제목", | ||
| description = "드라마 제목 드라마 제목 드라마 제목 드라마 제목 드라마 제목", | ||
| imageList = emptyList(), | ||
| bookmarkCount = 0, | ||
| isBookmarked = false, | ||
| userId = "0", | ||
| nickname = "nickname", | ||
| profileUrl = null | ||
| ) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
p3
CollectionItemModel에 Fake 객체를 만들고,
| val FakeList = CollectionListModel( | |
| collections = persistentListOf( | |
| CollectionItemModel( | |
| id = "0", | |
| thumbnailUrl = "", | |
| title = "드라마 제목", | |
| description = "드라마 제목 드라마 제목 드라마 제목 드라마 제목 드라마 제목", | |
| imageList = emptyList(), | |
| bookmarkCount = 0, | |
| isBookmarked = false, | |
| userId = "0", | |
| nickname = "nickname", | |
| profileUrl = null | |
| ) | |
| ) | |
| ) | |
| val FakeList = CollectionListModel( | |
| collections = persistentListOf( | |
| CollectionItemMode.Fake | |
| ) | |
| ) |
식으로 작성해봐도 좋을 것 같아요
| recommendCollectionModelList = recommendedCollectionList, | ||
| recentCollectionModelList = recentCollectionList, | ||
| savedContentModelList = bookmarkedContentList, |
There was a problem hiding this comment.
p2
화면에 표시될 데이터들은 Screen에 uiState로 묶어서 한번에 넘기는 방식은 어떨까요?
| private val _recommendCollectionListLoadState = MutableStateFlow<UiState<CollectionListModel>>(UiState.Loading) | ||
| private val _bookmarkedContentListLoadState = MutableStateFlow<UiState<BookmarkedContentListModel>>(UiState.Loading) | ||
| private val _recentCollectionListLoadState = MutableStateFlow<UiState<CollectionListModel>>(UiState.Loading) |
There was a problem hiding this comment.
p3
HomeUiState에 이미 얘네들이 다 들어가 있는 거 아닌가요??
copy 메서드를 써서 구현할 수도 있었을 거 같은데, 이렇게 구현하신 이유가 궁금합니다
| .onSuccess { data -> | ||
| if (data.isRegistered) { | ||
| _navigationEvent.emit(UiState.Success(LoginNavigationEvent.NavigateToHome)) | ||
| preferencesManager.saveString(USER_NAME, data.userName.toString()) |
There was a problem hiding this comment.
p2
saveString의 책임이 레포지토리일 수 있지 않을까요? ViewModel에서 구현하신 이유가 있으신지 궁금합니다
| @@ -54,34 +47,32 @@ fun HomeRoute( | |||
|
|
|||
| when (uiState.loadState) { | |||
| is UiState.Success -> { | |||
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/flint/data/di/interceptor/TokenInterceptor.kt (1)
29-30: 🚨 보안 취약점: 하드코딩된 JWT 토큰을 즉시 제거해야 합니다.프로덕션 코드에 JWT 토큰이 하드코딩되어 있습니다. 이 토큰은 민감한 사용자 정보(
userId,role)를 포함하고 있으며, 소스 코드에 커밋되어서는 안 됩니다.또한, 26-28번 라인에서 동적으로 설정한 토큰을 이 라인에서 덮어쓰고 있어 정상적인 인증 흐름이 무시됩니다.
🔒 수정 제안
if (accessToken.isNotEmpty()) { requestBuilder.header("Authorization", "Bearer $accessToken") } - // sample - requestBuilder.header("Authorization", "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOjgwMDAyMTcxMTM1NzExODE5MSwicm9sZSI6IkZMSU5HIiwidHlwZSI6IkFDQ0VTUyIsImlhdCI6MTc2ODgzODU0MCwiZXhwIjoxNzcwMDQ4MTQwfQ.A2XYyu24IoGAXNSQHJ1S-iudWmg8II2_ivI4EdyyWw9KS9oJlxHKOAhcKrsLpLkc9kllZyxwaTJO1t4vI7oZlg")
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/flint/domain/repository/AuthRepository.kt`:
- Line 24: The call that writes the username uses result.userName.toString(),
which will store the literal "null" if userName is null; update the persistence
to use a null-safe conversion such as result.userName.orEmpty() (or otherwise
handle the null before calling preferencesManager.saveString) so
preferencesManager.saveString(USER_NAME, ...) receives an empty string instead
of "null" — locate the line in AuthRepository where
preferencesManager.saveString(USER_NAME, result.userName.toString()) is called
and replace the toString() usage with a null-safe alternative.
- Around line 22-27: The socialVerify function calls api.socialVerify() outside
the suspendRunCatching, so network exceptions can escape and crash the app; wrap
the entire API call and mapping inside suspendRunCatching (i.e., call
api.socialVerify(model.toDto()), access .data.toModel(), and only on success
call preferencesManager.saveString(USER_NAME, result.userName.toString())) so
that exceptions are captured and a Result<SocialVerifyResponseModel> is
returned; keep references to the existing symbols: function socialVerify,
api.socialVerify, suspendRunCatching, preferencesManager.saveString, and the
Result<SocialVerifyResponseModel> return type.
🧹 Nitpick comments (2)
app/src/main/java/com/flint/data/local/PreferencesManager.kt (1)
25-28: DataStore 읽기 시 예외 처리를 고려해 주세요.
dataStore.data는 디스크 I/O 오류 시IOException을 throw할 수 있습니다. 현재 구현에서는 호출자가 예외를 처리해야 합니다. 필요시catch블록을 추가하거나, 호출부에서 예외 처리가 되어 있는지 확인해 주세요.♻️ 예외 처리 추가 예시
fun getString(key: String): Flow<String> = - dataStore.data.map { preferences -> - preferences[stringPreferencesKey(key)].orEmpty() - } + dataStore.data + .catch { exception -> + if (exception is IOException) { + emit(emptyPreferences()) + } else { + throw exception + } + } + .map { preferences -> + preferences[stringPreferencesKey(key)].orEmpty() + }app/src/main/java/com/flint/presentation/home/HomeViewModel.kt (1)
60-88: 실패 시 에러 상태를 UI에 전달하는 것을 고려해 주세요.현재
onFailure핸들러에서 로그만 남기고 있어, 사용자에게 에러 상태가 표시되지 않습니다.UiState.Error를 emit하여 UI에서 적절한 피드백을 제공할 수 있습니다.♻️ 에러 상태 처리 예시
fun getRecommendedCollectionList() = viewModelScope.launch { homeRepository.getRecommendedCollectionList() .onSuccess { _recommendCollectionListLoadState.emit(UiState.Success(it)) } .onFailure { Log.d("Logd", it.message.toString()) + _recommendCollectionListLoadState.emit(UiState.Error(it.message)) } }다른 함수들에도 동일하게 적용해 주세요.
| suspend fun socialVerify(model: SocialVerifyRequestModel): Result<SocialVerifyResponseModel> { | ||
| val result = api.socialVerify(model.toDto()).data.toModel() | ||
| preferencesManager.saveString(USER_NAME, result.userName.toString()) | ||
|
|
||
| return suspendRunCatching { result } | ||
| } |
There was a problem hiding this comment.
예외 처리가 누락되어 API 호출 실패 시 크래시가 발생합니다.
api.socialVerify() 호출이 suspendRunCatching 블록 바깥에 있어서, 네트워크 오류나 API 실패 시 예외가 전파되어 앱이 크래시될 수 있습니다. 현재 suspendRunCatching은 이미 계산된 result만 감싸고 있어 의미가 없습니다.
🐛 수정 제안
suspend fun socialVerify(model: SocialVerifyRequestModel): Result<SocialVerifyResponseModel> =
- val result = api.socialVerify(model.toDto()).data.toModel()
- preferencesManager.saveString(USER_NAME, result.userName.toString())
-
- return suspendRunCatching { result }
+ suspendRunCatching {
+ val result = api.socialVerify(model.toDto()).data.toModel()
+ preferencesManager.saveString(USER_NAME, result.userName.orEmpty())
+ result
+ }🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/domain/repository/AuthRepository.kt` around lines
22 - 27, The socialVerify function calls api.socialVerify() outside the
suspendRunCatching, so network exceptions can escape and crash the app; wrap the
entire API call and mapping inside suspendRunCatching (i.e., call
api.socialVerify(model.toDto()), access .data.toModel(), and only on success
call preferencesManager.saveString(USER_NAME, result.userName.toString())) so
that exceptions are captured and a Result<SocialVerifyResponseModel> is
returned; keep references to the existing symbols: function socialVerify,
api.socialVerify, suspendRunCatching, preferencesManager.saveString, and the
Result<SocialVerifyResponseModel> return type.
| suspendRunCatching { api.socialVerify(model.toDto()).data.toModel() } | ||
| suspend fun socialVerify(model: SocialVerifyRequestModel): Result<SocialVerifyResponseModel> { | ||
| val result = api.socialVerify(model.toDto()).data.toModel() | ||
| preferencesManager.saveString(USER_NAME, result.userName.toString()) |
There was a problem hiding this comment.
toString() 대신 orEmpty()를 사용하세요.
result.userName이 null인 경우, toString()은 문자열 "null"을 저장합니다. 의도한 동작이 아니라면 orEmpty()나 null-safe 처리를 사용해야 합니다.
🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/domain/repository/AuthRepository.kt` at line 24,
The call that writes the username uses result.userName.toString(), which will
store the literal "null" if userName is null; update the persistence to use a
null-safe conversion such as result.userName.orEmpty() (or otherwise handle the
null before calling preferencesManager.saveString) so
preferencesManager.saveString(USER_NAME, ...) receives an empty string instead
of "null" — locate the line in AuthRepository where
preferencesManager.saveString(USER_NAME, result.userName.toString()) is called
and replace the toString() usage with a null-safe alternative.
📮 관련 이슈
📌 작업 내용
Summary by CodeRabbit
새 기능
스타일
✏️ Tip: You can customize this high-level summary in your review settings.